study/java

자바/JAVA 스레드(2)

올스왑 2021. 2. 13. 05:08

스레드는 다양한 상태를 가지게 된다.

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

 

스레드 객체를 생성하고 start() 메서드를 호출하면 바로 스레드가 실행되는 것이 아니라 실행 대기 상태가 된다.

  • 실행 대기 상태(Runnable): 바로 실행할 준비가 된 상태. 실행을 기다리는 상태.
  • 실행 상태(Running): 실행 대기 상태인 스레드 중에서 OS가 하나를 선택하고 CPU가 스레드의 run() 메서드를 실행시킨다. 이때가 실행 상태이다.

실행 상태 스레드는 run() 메소드를 모두 실행하기 전에 실행 대기 상태로 돌아갈 수 있고, 실행 대기 상태에 있는 다른 스레드가 선택되어 실행 상태가 되기도 한다. 따라서 스레드는 실행 대기 상태와 실행 상태를 번갈아가면서 자신의 run() 메서드를 조금씩 실행한다. run() 메서드가 종료되면, 스레드의 실행이 멈추고 종료 상태(terminated)가 된다.

 

실행 대기 상태와 실행 상태를 번갈아 변하면서, 실행 상태에서 일시 정지 상태로 가기도 한다.

  • 일시 정지 상태(Not Runnable): 스레드가 실행할 수 없는 상태이다. 바로 실행 상태로 돌아갈 수 없고, 일시 정지 상태에서 빠져나와 실행 대기 상태로 가야한다.

join() 메소드: 해당 실행 스레드에서 다른 실행 스레드에 join을 거는 메소드이다. 이 메소드가 호출되면, join을 당한 스레드의 실행이 끌날 때까지 join을 건 스레드는 일시 정지 상태가 된다. 그리고 join 당한 스레드가 종료되면, join을 건 스레드가 실행된다.

-> 예를 들어, 메인 스레드에서 A 스레드를 실행하고 그 스레드의 결과값을 사용해야 할 때, 메인 스레드에서 A 스레드를 실행하고, 바로 그 값을 연산에 사용한다면 A 스레드가 연산을 다 마치지 못한 상태의 값을 사용할 수 있다. 그래서 threadA.join()을 실행하고 A 스레드의 실행이 끝날 때까지 메인 스레드는 대기하고, 실행이 끝나면 그 결과값을 사용해 작업을 해야 정상적으로 작동할 수 있다.

 

sleep(long millis) 메서드: 주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 시간이 지나면 자동으로 실행 대기 상태로 바꾼다. 정적 메서드이다.(실행 상태 -> 일시 정지 상태)
sleep() 메서드로 일시 정지 상태로 만들고, 주어진 시간이 끝나기 전에 interrupt() 메서드가 호출되면 InterruptedException이 발생될 수 있어 이 메서드를 사용하려면 예외 처리가 필요하다.

 

interrupt() 메서드: 일시 정지 상태의 스레드에서 InterruptedException을 발생시켜, 예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 가게 한다.((일시 정시 상태) -> (실행 대기 상태 OR 종료 상태))

 

stop() 메소드: 스레드를 즉시 종료한다. 불완전한 종료를 유발하므로 되도록 사용하지 않음.

 

 

 

스레드를 안전하게 종료하는 방법.

 

1. stop 플래그를 이용한다.

 

스레드의 필드 부분에 stop이라는 boolean 타입의 플래그를 선언하고 run() 메서드에서 while문 안에 스레드가 반복할 내용을 넣는다. 해당 스레드를 호출하는 스레드가 stop 플래그를 설정해 스레드를 종료한다.

 

public class myThread extends Thread{
	private boolean stop; // 플래그
    
    public void setStop(boolean stop) { // 세터 메소드
		this.stop = stop;
	}
    
    public void run(){
    	while(!stop){
        	//스레드가 반복 실행할 코드
        }
    }
}

 

2. interrupt() 메서드를 이용하는 방법.

 

interrupt() 메서드가 호출되면, 해당 스레드가 (sleep()에 의해) 일시 정지 상태가 될 때 catch 블록(예외 처리 코드)으로 이동한다. 그러면 run() 메서드의 while문을 탈출해 run() 메서드를 종료한다.

* 스레드가 실행 상태 또는 실행 대기 상태일 때, interrupt() 메서드가 실행되도 즉시 InterruptedException이 발생하는 것이 아니다. 이후에 일시 정지 상태가 되면 InterruptedException이 발생한다. 따라서 일시 정지 상태가 되지 않으면, interrupt() 메소드가 의미 없다.

 

myThread myt = new myThread();
myt.start();
...
myt.interrupt();



///////////////////////
//myThread 코드

public run(){
	try {
    	while(true){
        	...
            Thread.sleep(); //여기서 일시 정지 상태가 될 때, 예외가 발생
        }
    } catch(InterruptedException){} //while문 탈출
}

 

또는 interrupted(), isInterrupted() 메서드를 호출하면 interrupt() 메서드의 호출 여부에 따라 boolean 값을 리턴한다. 
interrupted()는 정적 메서드로 현재 스레드가 interrupted 되었는지 확인하고, isInterrupted()는 인스턴스 메소드로 같은 역할을 한다.
이 두 메서드를 사용하면 일시 정지 상태로 진입하지 않아도 스레드를 종료할 수 있다.

 

public void run(){
	while(true){
    	System.out.println("실행 중");
        if(Thread.interrupted()){
        	break;
        }
    }
}

 

 

 

데몬 스레드: 주 스레드의 작업을 돕는 보조 역할을 하는 스레드이다. 주 스레드가 종료되면 강제 종료됨. 이 점 외에는 일반 스레드와 큰 차이가 없다. 예를 들어, 워드의 자동 저장 기능 같은 것이다.

 

데몬 스레드를 만드는 방법은, 주 스레드에서 데몬이 될 스레드의 setDaemon(true)를 호출해주면 된다.
* 주의할 것은 데몬이 될 스레드의  setDaemon(ture)를 호출한 뒤 start() 메서드를 실행해야 한다. 그렇지 않으면 예외가 발생한다.

 

myThread myt = new myThread();
myt.setDaemon(true);
myt.start();


isDaemon() 이란 메서드로 데몬 스레드인지 구분할 수 있다.

 

 

wait(), notify() (Object 메소드)

wait() : 리소스가 더 이상 유효하지 않다면 리소스가 사용 가능할 때 까지 스레드 일시 정지 상태(non runnable)로 바꾼다. wait() 상태가 된 스레드는 notify()가 호출 될 때까지 기다린다.
notify
() : wait() 하고 있는 스레드 중 하나를 실행 대기 상태(runnable)로 깨운다.
notifyAll() : wait() 하고 있는 모든 스레드를 깨운다.

notify() 보다 notifyAll()을 사용하기를 권장한다. 특정 스레드만 제어할 수 없어 모두 스레드를 깨운 뒤, 스케줄러에 의해 CPU를 점유하는 것이 공평하다.