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이 발생하여 종료하는 것은 아닌 것 같다. 아시는분 댓글로 알려주시면 감사하겠습니다!
'■ JAVA > Study' 카테고리의 다른 글
[JAVA] 멀티 스레드 - (2)작업 스레드 생성과 실행 (0) | 2020.04.02 |
---|---|
[JAVA] 스레드 - sleep(), yield(), join(), wait(), notify(), notifyAll(), interrupt() (0) | 2020.04.01 |
[JAVA] do-while (3) (0) | 2019.12.19 |
[JAVA] do-while (2) (0) | 2019.12.19 |
[JAVA] do-while (1) (0) | 2019.12.18 |