Thread_2

jaegeunsong97·2023년 1월 15일
0
post-thumbnail

2023_1_15_TIL

동기화의 개념

  • 동기화(synchronized) -> 하나의 작업이 완전히 완료된후 다른 작업을 수행하는 것
  • 비동기화(asynchronous) -> 하나의 작업 명령 후 완료 여부와 상관없이 바로 다른 작업 명령을 수행

동기화의 필요성

  • 동기화를 사용하지 않았을 때 -> 스레드가 겹침(원하는 값이 나오지 않음)
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(getName() + ": " + (isDaemon() ? "데몬스레드" : "일반스레드"));
        for (int i = 0; i < 6; i++) {
            System.out.println(getName() + ": " + i + "초");
            try {Thread.sleep(1000);} catch (InterruptedException e){}
        }
    }
}

public class ThreadProperties_3_3 {
    public static void main(String[] args) {
        Thread thread1 = new MyThread();
        thread1.setDaemon(false);
        thread1.setName("thread1");
        thread1.start();

        Thread thread2 = new MyThread();
        thread2.setDaemon(true);
        thread2.setName("thread2");
        thread2.start();

        try {Thread.sleep(3500);} catch (InterruptedException e){}
        System.out.println("main Thread 종료");
    }
}

동기화 방법

  • 하나의 스레드가 메소드 or 블록 사용을 종료 후 잠금이 풀리면 다른 스레드가 사용할 수 있는 것
  • 메소드 동기화
    • 2개의 스레드가 동시에 메소드를 실행 X
    • 메소드 앞에 synchronized 붙이기
class MyData {
    int data = 3;
    public synchronized void plusData() {
        int mydata = data;
        try {Thread.sleep(2000);} catch (InterruptedException e){}
        data = mydata + 1;
    }
}

class PlusThread extends Thread {
    MyData mydata;
    public PlusThread(MyData mydata) {
        this.mydata = mydata;
    }

    @Override
    public void run() {
        mydata.plusData();
        System.out.println(getName() + "실행결과: " + mydata.data);
    }
}

public class SynchronizedMethod {
    public static void main(String[] args) {
        MyData myData = new MyData();
        Thread plusThread1 = new PlusThread(myData);
        plusThread1.setName("plusThread1");
        plusThread1.start();

        try {Thread.sleep(1000);} catch (InterruptedException e){}

        Thread plusThread2 = new PlusThread(myData);
        plusThread2.setName("plusThread2");
        plusThread2.start();
    }
}
  • 블록 동기화
    • 꼭 필요한 부분에만 적용하는 것 -> 부분만 동기화하는 방법
    • 2개의 스레드가 동시에 해당 블록을 실행 X
    • 일반적으로 this를 넣어 자기객체를 가리킴 -> this는 생성과정없이 바로 사용할 수 있어서
class MyData {
    int data = 3;
    public void plusData() {
        synchronized (this) {// <<--
            int mydata = data;
            try {Thread.sleep(2000);} catch (InterruptedException e){}
            data = mydata + 1;
        }
    }
}

class PlusThread extends Thread {
    MyData mydata;
    public PlusThread(MyData mydata) {
        this.mydata = mydata;
    }
    @Override
    public void run() {
        mydata.plusData();
        System.out.println(getName() + "실행결과: " + mydata.data);
    }
}

public class SynchronizedBlock {
    public static void main(String[] args) {
        MyData myData = new MyData();

        Thread plusThread1 = new PlusThread(myData);
        plusThread1.setName("plusThread1");
        plusThread1.start();

        try {Thread.sleep(1000);} catch (InterruptedException e){}

        Thread plusThread2 = new PlusThread(myData);
        plusThread2.setName("plusThread2");
        plusThread2.start();
    }
}

동기화의 원리

  • 모든 객체는 자신만의 Key를 가짐
    • 블록 동기화(synchronized(this){} -> 블록이 this객체가 갖고 있는 열쇠로 잠김
    • 첫 번째로 동기화 block을 실행하는 스레드가 key를 가짐 -> 해당 스레드가 블록 완료전까지 다른 스레드는 key 못 얻음
    • 메소드를 동기화 하는 경우 -> this객체의 열쇠만 사용
      • 하나의 객체 내부에 3개의 동기화 메소드 있다? -> 모두 this의 열쇠로 잠겨 있음 -> 1개의 스레드가 해당 메소드들 중 1개만 실행해도 나머지 모두 잠김
// 3개의 동기화 영역이 동일한 열쇠로 동기화
class MyData {
    synchronized void abc() {
        for (int i = 0; i < 3; i++) {
            System.out.println(i + "sec");
            try {Thread.sleep(1000);} catch (InterruptedException e){}
        }
    }

    synchronized void bcd() {
        for (int i = 0; i < 3; i++) {
            System.out.println(i + "초");
            try {Thread.sleep(1000);} catch (InterruptedException e){}
        }
    }

    void cde() {
        synchronized (this) {
            for (int i = 0; i < 3; i++) {
                System.out.println(i + "번쨰");
                try {Thread.sleep(1000);} catch (InterruptedException e){}
            }
        }
    }
}

public class KeyObject_1 {
    public static void main(String[] args) {
        MyData myData = new MyData();

        new Thread() {
          public void run() {
              myData.abc();
          }
        }.start();

        new Thread() {
            public void run() {
                myData.bcd();
            }
        }.start();

        new Thread() {
            public void run() {
                myData.cde();
            }
        }.start();
    }
}

// 동기화 메서드와 동기화 블록이 다른 열쇠 사용
class  MyData {
    synchronized void abc() {
        for (int i = 0; i < 3; i++) {
            System.out.println(i + "sec");
            try {Thread.sleep(1000);} catch (InterruptedException e){}
        }
    }
    synchronized void bcd() {
        for (int i = 0; i < 3; i++) {
            System.out.println(i + "초");
            try {Thread.sleep(1000);} catch (InterruptedException e){}
        }
    }
    void cde() {
        synchronized (new Object()) {// <<-- 혼자 key 다름
            for (int i = 0; i < 3; i++) {
                System.out.println(i + "번쨰");
                try {Thread.sleep(1000);} catch (InterruptedException e){}
            }
        }
    }
}

public class KeyObject_2 {
    public static void main(String[] args) {
        MyData myData = new MyData();

        new Thread() {
            public void run() {
                myData.abc();
            }
        }.start();

        new Thread() {
            public void run() {
                myData.bcd();
            }
        }.start();

        new Thread() {
            public void run() {
                myData.cde();
            }
        }.start();
    }
}

쓰레드의 6가지 상태

  • 스레드 상태 값 가져오기 -> Thread클래스 인스턴스 메소드
    • Thread.State getState()
  • NEW, RUNNABLE, TERMINATED
    • NEW -> 객체가 처음 생성
    • RUNNABLE -> start()로 실행하면(실행과 대기를 반복, CPU를 다른 스레드와 나눠 사용)
      • 3가지의 일시정지 상태 -> TIMED_WAITING, BLOCKED, WAITING
    • TERMINATED -> run()종료되면 이 상태
  • TIMED_WAITING
    • 일정시간정지
    • 해당 메소드로 일시정지되는 대상 -> 해당 메소드를 호출한 스레드
    • static void Thread.sleep(long millis) -> 정적 메소드
    • synchronized void join(long millis) -> 인스턴스 메소드
    • RUNNABLE로 전환
      • 시간이 다 지남
      • void interrupt()
  • BLOCKED
    • 동기화 메소드, 블록을 실행하기 위해 먼저 실행 중인 스레드의 실행 완료를 기다리는 것
    • 실행 중인 스레드가 key반납할 떄 까지 기다리고 있는 상태
  • WAITING
    • 시간을 정하지 않아 무지성으로 계속 기다려야 함
    • synchronized void join()
      • join대상의 스레드 종료
      • void interrupt()
    • void wait() -> Object 클래스로부터 상속받은 메소드
      • wait대상의 스레드 종료
      • void notify() -> 하나만 RUNNABLE
      • void notifyAll() -> 모두 RUNNABLE
      • 해당 메소드는 동기화 블록 내에서만 사용 가능

NEW, RUNNABLE, TERMINATED

public class NewRunnableTerminated {
    public static void main(String[] args) {
        Thread.State state;

        Thread myThread = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 1000000000L; i++) {}
            }
        };
        state = myThread.getState();
        System.out.println("myThread state = " + state);

        myThread.start();
        state = myThread.getState();
        System.out.println("getThread state = " + state);

        try {
            myThread.join();// <<--
        } catch (InterruptedException e) {}

        state = myThread.getState();
        System.out.println("getThread state = " + state);

    }
}
  • static void Thread.yield() -> 한 번 자기 차례 양보(자기는 실행 대기 상태)
class MyThread extends Thread {
    boolean yieldFlag;
    @Override
    public void run() {
        while(true) {
            if (yieldFlag) {
                Thread.yield();
            } else {
                System.out.println(getName() + " 실행");
                for (int i = 0; i < 100000000L; i++) {}
            }
        }
    }
}

public class YieldInRunnableState {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        thread1.setName("thread1");
        thread1.yieldFlag = false;
        thread1.setDaemon(true);
        thread1.start();

        MyThread thread2 = new MyThread();
        thread2.setName("thread2");
        thread2.yieldFlag = true;
        thread2.setDaemon(true);
        thread2.start();

        for (int i = 0; i < 6; i++) {
            try {Thread.sleep(1000);} catch (InterruptedException e){}
            thread1.yieldFlag = !thread1.yieldFlag;
            thread2.yieldFlag = !thread2.yieldFlag;
        }
    }
}
  • 스레드의 실행이 종료? -> 스레드 객체는 남아있다
    • 다시 스레드 원한다? -> 새로운 객체 만들어야함

TIMED_WAITING

  • 모두 InterruptedException을 처리해줘야함 -> interrupt()를 호출하기 때문에
  • Thread.sleep(long millis)를 이용한 일시정지 및 RUNNABLE 상태 전환
    • 일시정지 시간 동안 CPU를 어떤 스레드가 사용하든 상관 X
    • 스레드 준비 시간을 줘야함 -> start()와 interrupt()에 0.1초 지연이유
      • 전달 되기전까지는 TIMED_WAITING상태이기 때문에(0.1초 지연)
      • 스레드 처음 시작시 JVM이 CPU를 독립적으로 사용해서 메모리 할당과 같은 시간이 필요
      • 모든 준비 끝나야 run() 실행
      • 만약 준비 안 끝나고 start() 실행? -> RUNNABLE 가능성 ㅇㅇ
// Thread.sleep(long millis)
class MyThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println(" -- sleep() 진행 중 interrupt() 발생");
            for (long i = 0; i < 1000000000L; i++) {}
        }
    }
}

public class TimedWaiting_Sleep {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        try {Thread.sleep(100);} catch (InterruptedException e){}
        System.out.println("MyThread State = " + myThread.getState());

        myThread.interrupt();

        try {Thread.sleep(100);} catch (InterruptedException e){}
        System.out.println("MyThread State = " + myThread.getState());
    }
}
  • join(long millis)를 이용한 일시정지 및 RUNNABLE 상태전환
    • 특정 스레드 객체에게 일정 시간 동안 CPU를 할당하라는 의미
      • 특정 스레드가 작업이 일정 시간보다 빨리 끝나면 정지한 스레드는 RUNNABLE 상태로 변경
    • 다른 스레드의 결과가 먼저 나와야 이후의 작업을 진행할 때 사용
class MyThread1 extends Thread {
    @Override
    public void run() {
        for (long i = 0; i < 1000000000L; i++) {}
    }
}

class MyThread2 extends Thread {
    MyThread1 myThread1;
    public MyThread2(MyThread1 myThread1) {
        this.myThread1 = myThread1;
    }
    @Override
    public void run() {
        try {
            myThread1.join(3000);
        } catch (InterruptedException e) {
            System.out.println(" -- join(...) 진행 중 interrupt() 발생");
            for (long i = 0; i < 1000000000L; i++) {}
        }
    }
}

public class TimedWaiting_Join {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        MyThread2 myThread2 = new MyThread2(myThread1);

        myThread1.start();
        myThread2.start();

        try {Thread.sleep(100);} catch (InterruptedException e){}
        System.out.println("MyThread1 State = " + myThread1.getState());
        System.out.println("MyThread2 State = " + myThread2.getState());

        myThread2.interrupt();
        try {Thread.sleep(100);} catch (InterruptedException e){}
        System.out.println("MyThread1 State = " + myThread1.getState());
        System.out.println("MyThread2 State = " + myThread2.getState());
    }
}

BLOCKED

  • 먼저 도착하는 스레드가 실행 권한을 가짐
    • key가 반납되는 순간 BLOCKED 상태의 모든 스레드가 다시 경쟁해 열쇠를 먼저 차지하느 스레드가 실행
    • 실행 순서와 상관없음
class MyBlockTest {
    MyClass mc = new MyClass();

    Thread t1 = new Thread("thread1") {
        public void run() {
            mc.syncMethod();
        }
    };
    Thread t2 = new Thread("thread2") {
        public void run() {
            mc.syncMethod();
        }
    };
    Thread t3 = new Thread("thread3") {
        public void run() {
            mc.syncMethod();
        }
    };

    void startAll() {
        t1.start();
        t2.start();
        t3.start();
    }

    class MyClass {
        synchronized void syncMethod() {
            try {Thread.sleep(100);} catch (InterruptedException e){}
            System.out.println("====" + Thread.currentThread().getName() + "====");
            System.out.println("thread1 -> " + t1.getState());
            System.out.println("thread2 -> " + t2.getState());
            System.out.println("thread3 -> " + t3.getState());
            for (long i = 0; i < 1000000000L; i++) {}
        }
    }
}

public class BlockedState {
    public static void main(String[] args) {
        MyBlockTest myBlockTest = new MyBlockTest();
        myBlockTest.startAll();
    }
}

WAITING

  • wait()주의점
    • 다른 스레드에서 notify() or notifyAll()을 호출해야 RUNNABLE 상태가 됨
      • 다른 스레드에서 notify() or notifyAll() 되면, 일시정지 됐던 지점인 wait()의 다음 줄부터 실행
      • 즉, 처음부터 다시 실행되는 것이 아님 -> wait() 위치 신경 쓸 것
      • 스스로 RUNNABLE상태 X
    • wait(), notify(), notifyAll() -> 동기화 블록에서만 사용가능
  • 언제 wait() notify() 사용? -> 아래 코드의 예시를 보자
// 동기화만을 사용했을 떄의 임의적인 두 쓰레드 실행 순서 -> 동시에 일어나지 않음
class DataBox {
    int data;
    synchronized void inputData(int data) {
        this.data = data;
        System.out.println("입력 데이터: " + data);
    }
    synchronized void outputData() {
        System.out.println("출력 데이터: " + data);
    }
}

public class Waiting_WaitNotify_1 {
    public static void main(String[] args) {
        DataBox dataBox = new DataBox();
        Thread t1 = new Thread() {
            public void run() {
                for (int i = 1; i < 9; i++) {
                    dataBox.inputData(i);
                }
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                for (int i = 1; i < 9; i++) {
                    dataBox.outputData();
                }
            }
        };

        t1.start();
        t2.start();
    }
}

// wait(), notify() 사용한 스레드의 교차실행 -> 서로 번갈아 가면서 나옴
class DataBox {
    boolean isEmpty = true;
    int data;
    synchronized void inputData(int data) {
        if (!isEmpty) {
            try {wait();} catch (InterruptedException e){}
        }
        this.data = data;
        isEmpty = false;
        System.out.println("입력 데이터: " + data);
        notify();
    }
    synchronized void outputData() {
        if (isEmpty) {
            try {wait();} catch (InterruptedException e){}
        }
        isEmpty = true;
        System.out.println("출력 데이터: " + data);
        notify();
    }
}

public class Waiting_WaitNotify_2 {
    public static void main(String[] args) {
        DataBox dataBox = new DataBox();
        Thread t1 = new Thread() {
            public void run() {
                for (int i = 1; i < 9; i++) {
                    dataBox.inputData(i);
                }
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                for (int i = 1; i < 9; i++) {
                    dataBox.outputData();
                }
            }
        };

        t1.start();
        t2.start();
    }
}

참조

https://jongwoon.tistory.com/14

profile
현재 블로그 : https://jasonsong97.tistory.com/

0개의 댓글