1. 스레드 상태제어

- 실행중인 스레드의 상태를 변경하는 것.

- 메소드로 주로 제어

- interrupt(), sleep(), join(), wait(), yield() notify(), notifyAll() 등의 메소드가 존재.

- 이중 notify(), notifyAll(), wait() 메소드는 Object 클래스의 메소드이고 나머지는 Thread 클래스의 메소드.

 

 

 

 

2. 일정시간동안 일시정지 : sleep()

- 실행중인 스레드를 일시정지.

- 매개값으로 밀리초를 넣어주면 해당 시간동안 sleep() 메소드를 만나는 스레드는 일시정지함.

- 일시정지 상태에서 interrupt() 메소드를 호출할 경우 InterruptedException이 발생됨.

try{
Thread.sleep(1000);  //1초간 일시정지(밀리초 : 1000 -> 1초)
}catch(InterruptedException){
     //interrupt() 메소드가 호출되면 실행.
}

 

 

 

 

3. 타 스레드에 실행 양보 : yield() 

- 스레드가 처리하는 반복작업을 위해 for문이나 while문을 사용하는 경우가 많음.

public void run(){
     while(true){
          if(work){
              System.out.println("ThreadA 작업 내용");
          }
     }
}

 

-이때 while문은 boolean 타입 work 변수가 false 일경우에는 쓸데없는 루프를 돌게됨.

- yield() 메소드를 호출하면 호출한 스레드는 실행대기상태로 돌아가고 동일한 우선순위 혹은 높은 우선순위를 갖는 다른 스레드가 실행 기회를 갖게됨.

public class ThreadA extends Thread{
        public boolean stop = false; //종료 플래그
        public boolean work = true; // 작업진행여부

          public void run() {
                while(!stop) {
                        if(work) {
                           System.out.println("ThreadA 작업 내용");
                        }else {
                           Thread.yield();
                        }
                }
            System.out.println("ThreadA 종료");
       }
}

- ThreadA가 else 문으로 yield 메소드를 만나면 현재 Thread 객체인 ThreadA의 run 메소드는 실행대기 상태가 되고 ThreadB가 실행기회를 갖는다.

 

 

 

 

4. 다른 스레드의 종료를 기다림 : join()

- 다른 스레드가 종료되어야 실행해야하는 스레드가 존재

- 계산작업이 그 예인데, 계산하여 결과를 return 하는 스레드가 존재하면 그것을 출력하는 스레드가 필요한데

- 그 때 출력스레드가 먼저 수행되면 오류임.

public class SumThread extends Thread{
     private long sum;

     public long getSum() {
          return sum;
     }

     public void setSum(long sum) {
          this.sum = sum;
     }

     public void run() {
          for(int i =1; i<=100; i++) {
               sum+=i;
          }
     }

     @Override
     public String toString() {
          return "SumThread [sum=" + sum + "]";
     }
}

public class JoinExample {
     public static void main(String[] args) {
          SumThread sumThread = new SumThread();
          sumThread.start();     

          try {
               sumThread.join();//현재 스레드 기준 (이부분을 주석처리해서 결과를 비교해보세요)
          } catch (Exception e) {
          }
          System.out.println("1~100 합 : "+sumThread.getSum());
     }
}

- 여기서 출력스레드는 메인스레드가 담당한 것임.

 

 

 

5. 스레드간 협력 : wait(), notify(), notifyAll()

- 두개의 스레드를 번갈아가면서 실행

- 핵심은 공유객체의 활용

- 두 스레드가 작업할 내용을 동기화 메소드로 구분.

- 스레드1 작업 완료 -> notify() 메소드 호출 -> (일시정지)스레드 2 실행대기상태로 변경 -> 스레드 1은 wait() (일시정지 상태)

- 이들 메소드는 동기화 메소드 혹은 동기화 블록에서만 사용가능.

//공유객체
public class WorkObject {
       public synchronized void methodA() {
              System.out.println("ThreadA의 methodA() 작업 실행");
              notify(); //일시정지 상태에 있는 ThreadB를 실행대기 상태로 만듬.
              try {
                     wait();//ThreadA를 일시정지 상태로 만듬.
              } catch (Exception e) {
              }
       }

       public synchronized void methodB() {
              System.out.println("ThreadB의 methodB() 작업 실행");
              notify(); //일시정지 상태에 있는 ThreadA를 실행대기 상태로 만듬.
              try {
                     wait();//ThreadB를 일시정지 상태로 만듬.
              } catch (Exception e) {
              }
       }
}

//Thread A
public class ThreadA extends Thread{
       private WorkObject workObject;

       public ThreadA(WorkObject workObject) {
              this.workObject = workObject;
       }

       @Override
       public void run() {
              for(int i =0; i<10; i++) {
                     workObject.methodA();
              }
       }
}

//ThreadB
public class ThreadB extends Thread{
       private WorkObject workObject;

       public ThreadB(WorkObject workObject) {
              this.workObject = workObject;
       }

       @Override
       public void run() {
              for(int i =0; i<10; i++) {
              workObject.methodB();
              }
       }
}

//main 스레드
public class WaitNotifyExample {
       public static void main(String[] args) {
              WorkObject shareObject = new WorkObject(); //공유객체 생성

              ThreadA threadA = new ThreadA(shareObject);
              ThreadB threadB = new ThreadB(shareObject);//ThreadA와 ThreadB 생성

              threadA.start();
              threadB.start();

       }
}

- 메인 스레드에서 공유객체를 생성

- 각각의 스레드의 멤버변수로 초기화. 공유 객체의 methodA와 methodB를 사용

- methodA와 methodB는 번갈아가면서 실행되어야함.

- 이 협력개념에서 발전하여 유명한 자바 디자인 패턴인 생산자 소비자 패턴으로 연결됨.

 

 

 

 

6. 스레드의 안전종료 : interrupt()

- run() 메소드가 모두 실행되면 스레드는 종료됨.

- 기존의 stop() 이란 메소드가 제공되었으나 deprecated 되었다. -> 문제

- 왜? -> 스레드가 사용하던 자원이 문제가 될 가능성( 자원이란 파일, 네트워크 연결 등)

- interrupt() 메소드를 이용하여 자원도 해제하며 안전하게 종료할 수 있음.

public class PrintThread2 extends Thread{
       public void run() {
              try {
                     while(true) {
                            System.out.println("실행 중");
                            Thread.sleep(1);
                            //if(Thread.interrupted()) {
                            //if(Thread.currentThread().isInterrupted()) {
                                   //break;
                            //}
                     }
              } catch (InterruptedException e) {
                     System.out.println("interrupt() 실행");
              }
              System.out.println("자원 정리");
              System.out.println("실행 종료");
       }
}

//메인 스레드
public class InterruptExample {
       public static void main(String[] args) {
              Thread thread = new PrintThread2();
              thread.start();

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

              }
              thread.interrupt();
       }
}

- 메인스레드에서 interrupt() 메소드가 호출할 때 PrintThread2 스레드는 InterruptedException이 발생하기 위해서는 일시정지상태에 있어야 한다. 그렇지 않으면 아무의미가 없다.

 

- 그래서 Thread.sleep(1) 코드로 한번 일시정지 상태를 만들어주고 메인스레드에서 interrupt() 메소드를 실행하고 먼저 종료하였기 때문에 이후 PrintThread2 스레드는 자원을 정리하는 코드를 실행하며 안전하게 종료하게 된다.

 

- 주석처리된 Thread.interrupted() 메소드와 isInterrupted() 메소드는 모두 interrupt() 메소드가 실행됬는지 여부를 반환하는 boolean 값이다. 참조객체를 보면알겠지만 Thread.interrupted()는 static 메소드이고, isInterrupted() 메소드는 인스턴스 메소드이다. 둘중 어떤것을 사용해도 좋다.

 

 

 

 

 

7. 데몬스레드

- 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드.

- 주 스레드가 종료되면 데몬스레드는 강제적으로 자동 종료.

- Java의 Garbage Collector가 대표적인 데몬 스레드라고 함. - > JVM이 종료되면 같이 종료되니까.

- 현재 스레드에서 다른 스레드를 데몬스레드로 만들기 위해서는 데몬 스레드가될 스레드의 참조객체에서 setDaemon(true)를 호출해주면 된다.

- 주의점은 데몬스레드의 스레드가 이미 start() 메소드를 호출한 상태라면 IllegalThreadStateException이 발생하기 때문에 start() 메소드를 호출하기 전에 setDaemon(true)를 실행해야한다.

public class AutoSaveThread extends Thread{
       public void save() {
              System.out.println("작업 내용을 저장함");
       }

       @Override
       public void run() {
              while(true) {
                     try {
                            Thread.sleep(1000);
                     } catch (InterruptedException e) {
                            System.out.println("1");//여기실행안됨. exception 발생은 아님
                            e.printStackTrace();
                            break;
                     }
                     save();
              }
       }
}

//메인 스레드
public class DaemonExample {
       public static void main(String[] args) {
              AutoSaveThread autoSaveThread = new AutoSaveThread();
              autoSaveThread.setDaemon(true);
              autoSaveThread.start();

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

              }
              System.out.println("메인 스레드 종료");
       }
}

- 코드를 실행해보면 기존의 스레드는 메인 스레드가 죽어도 반복작업을 하는 경우는 작업스레드는 살아있어 프로그램이 죽지 않았는데 위 예제는 메인 스레드가 죽으면서 데몬스레드도 같이 죽어서 프로그램을 종료시킨다.

 

- 데몬스레드가 종료하는 이유를 알고싶어서 Exception이 발생하는 것인가 확인차 catch 문에 프린트 소스를적어봤으나 Exception이 발생하여 종료하는 것은 아닌 것 같다. 아시는분 댓글로 알려주시면 감사하겠습니다!