본문 바로가기

Java/문법

15 - 5장 쓰레드의 상태

쓰레드의 상태 

- Thread.State 타입으로 정의

- Thread의 인스턴스 메서드인 getState()로 가져올 수 있음

 

- getState()

 -> 쓰레드의 상태를 Thread.State 타입에 저장된 문자열 상숫값 중 하나로 리턴

 -> Thread.State는 enum 타입

 -> Thread.State의 내부에는 6개의 문자열 상수 (NEW, RUNNABLE, TERMLNATED, TLMED_WAITING, BLOCKED, WAITING)가 저장

 

-쓰레드의 상태에 따른 동작 수행

Thread.State state = myThread.getState();
swich(state) {
case Thread.State.NEW;
	//...
case Thread.State.RUNNABLE;
	//...
case Thread.State.TERMINATED:
	//...
case Thread.State.TIMED_WAITING;
	//...
case Thread.State.BLOCKED;
	//...
case Thread.State.WAITING;
	//...
}

 

1. 쓰레드의 6가지 상태 

 

1) NEW, RUNNABLE, TERMINATED

 

 - 처음 객체가 생성되면 NEW의 상태를 가짐 (Thread myThread = new myThread)

 - start() 메서드로 실행하면 RUNNABLE 상태가 됨 

  -> 이 상태에서는 실행과 실행 대기를 반복하면서 CPU를 다른 쓰레드들과 나눠 사용 

 - run() 메서드가 종료되면 TERMINATED상태가 됨 

 

2) TIMED_WAITING

 

 - 정적 메서드인 Thread.sleep(long millis) 또는 인스턴스 메서드인 join(longmillis)가 호출되면 쓰레드는 TIMED_WAITING 상태가 됨

 - 일정 시간 동안 일시정지됨

 - RUNNABLE -> TIMED_WAITING

static void Thread.sleep(long millis)
synchronized void join(long millis)

 -설정한 일시정지 시간이 지나거나 중간에 interrupt() 메서드가 호출되면 다시 RUNNABLE 상태가 됨

 - TIMED_WAITING -> RUNNABLE

일시정지 시간이 종료
void interrupt()

- 쓰레드 A 객체. Thread.sleep(1000)

 -> 쓰레드 A는 외부에서 interrupt() 메서드가 호출되지 않는 한 1초 동안 일시정지 상태가 유지 

 

- 쓰레드 B 객체. join(1000)

 -> 나는 일시정지하고 쓰레드 B에게 CPU를 할당하라 

 -> 쓰레드 B가 실행이 완료된다면 외부 interrupt() 메서드의 호출 없이도 다시 쓰레드 A는 RUNNABLE 상태가 됨

 

3) BLOCKED

 

 - 동기화 메서드 또는 동기화 블록을 실행하기 위해 먼저 실행중인 쓰레드의 실행 완료를 기다리는 상태 

 - 앞의 쓰레드의 동기화 영역 수행이 완료되면 BlOCKED 상태의 쓰레드는 RUNNABLE 상태가 돼 해당 동기화 영역을 실행하게 됨

 

4) WAITING

 

 - 시간 정보가 없는 join()메서드가 호출되거나 wait() 메서드가 호출되면 WAITING상태가 됨

 - wait()는 Object 클래스로부터 상속받은 메서드 

 - RUNNABLE -> WAITING

synchronized void join()
void wait()

 - join() 메서드로 WAITING 상태가 됐을 때 WAITING -> RUNNABLE 상태로 전환

join()의 대상 쓰레드가 종료
void interrupt()

- wait() 메서드로 WAITING 상태가 됐을 때 WAITING -> RUNNABLE 상태로 전환

void notify()
void notifyAll()

 - notify()

 -> WAITING 상태에 있는 하나의 쓰레드를 RUNNABLE 상태로 전환하는 메서드 

 

 - notifyAll()

 -> WAITING 상태의 모든 쓰레드를 RUNNABLE 상태로 전환하는 메서드 

 

 - wait(), notify(), notifyAll()는 동기화 블록 내에서만 사용 가능 

 

2. NEW, RUNNALBE, TERMINATED

 

NEW new 키워드로 쓰레드의 객체가 생성된 상태 (start() 전)
RUNNABLE start() 이후 CPU를 사용할 수 있는 상태 
다른 쓰레드들과 동시성에 따라 실행과 실행대기를 교차함
TERMINATED run() 메서드의 작업 내용이 모두 완료돼 쓰레드가 종료된 상태 
한번 실행된 쓰레드는 재실행이 불가능하며 객체를 새롭게 생성해야 함

 

- 쓰레드 상태(NEW, RUNNABLE, TERMINATED)

package ThreadgetState;

public class ThreadgetState {
	public static void main(String[] args) {
		//쓰레드 상태 저장 클래스 
		Thread.State state;
		
		//1.객체 생성(NEW)
		Thread myThread = new Thread() {
			//override
			public void run() {
				for(long i = 0; i < 10000000L; i++) {} //시간 지연
			}
		};
		state = myThread.getState();
		System.out.println("myThread state="+state);
		
		//2.myThread 시작 
		myThread.start();
		state = myThread.getState();
		System.out.println("myThread state="+state);
		
		//3.myThread 종료 
		try {
			myThread.join(); //myThread 실행이 완료될 때 까지 main() 쓰레드 일시정지
		} catch(InterruptedException e) {}
		state = myThread.getState();
		System.out.println("myThread state="+state);
	}
}

 

- RUNNABLE 상태에서는 쓰레드 간의 동시성에 따라 실행과 실행 대기를 반복 

- Thread의 정적 메서드인 yield()를 호출하면 다른 쓰레드에게 CPU 사용을 인위적으로 양보하고, 자신은 실행 대기 상태로 전환

- 자신의 차례를 딱 한 번 양보하는 메서드로, 자신의 차례가 돌아오면 다시 CPU 사용 가능 

static void Thread.yield();

 -RUNNABLE 상태에서 yield() 메서드를 이용한 CPU 사용 양보 

 -> yieldFlag= true일때 다른 쓰레드에게 자신의 차례를 양보

 -> yieldFlag= false일떄 자신의 쓰레드의 이름을 출력하고 일정 시간 지연

package ThreadgetState;

class MyThread extends Thread{
	boolean yieldFlag;
	//Override
	public void run() {
		while(true) {
			if(yieldFlag) {
				Thread.yield(); //yieldFlag가 true이면 다른 쓰레드에게 CPU 사용권을 양보
			}else {
				System.out.println(getName() + "실행");
				for(long i = 0; i < 100000000L; i++) {} //시간 지연 
			}
		}
	}
}
public class ThreadgetState {
	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();
		
		//6초 지연(1초마다 한번 씩 양보)
		for(int i=0; i <6; i++) {
			try {Thread.sleep(1000);} catch(InterruptedException e) {}
			thread1.yieldFlag =! thread1.yieldFlag;
			thread2.yieldFlag =! thread2.yieldFlag;
		}
	}
}

 

3. TIMED_WAITING

 

- RUNNABLE상태에서 TIMED_WAITING 상태가 됐을 때는 Thread의 정적 메서드인 sleep(long millis)와 인스턴스 메서드인  join(long millis)가 호출됐을 때 

 

- Thread.sleep()

 -> 메서드를 호출한 쓰레드를 일시정지 

 -> 호출한 쓰레드는 TIMED_WAITING이 됨

 -> 일시정지 시간 동안 CPU를 어떤 쓰레드가 사용하든 상관하지 않음

 -> InterruptedException 예외 처리 필요

 

- 객체.join(long millis)

 -> 특정 쓰레드 객체에게 일정 시간 동안 CPU를 할당

 -> 다른 쓰레드의 결과가 먼저 나와야 이후의 작업을 진행할 수 있을 때 사용

 -> 이 메서드를 호출한 쓰레드도 TIMED_WAITING 상태가 됨 

 -> InterruptedException 예외 처리 필요 

 

-interrupt() 메서드 

 -> InterruptedException 예외가 발생해 일시정지가 종료

 

1) Thread.sleep(long millis)를 이용한 일시정지 및 RUNNABLE 상태 전환

 

 - run() 메서드의 시작과 동시에 Thread.sleep(3000) 쓰레드 시작과 동시에 3초 동안 일시정지(TIMED_WAITING)상태가 되는 MyThread 클래스 정의 

 

 - 쓰레드를 시작한 후 Thread.sleep(100)

 -> 쓰레드를 처음 시작할 때 자바 가상 머신이 CPU를 독립적으로 사용하기 위한 메모리 할당 등의 준비 과정을 미리 거쳐야 하기 때문

 -> 이 준비가 끝나야 run()메서드 실행

 

 - interrupt() 메서드 호출 이후 Thread.sleep(100)

  -> interrupt() 메서드를 호출하면 자바 가상 머신은 InterruptedException 객체를 생성해 해당 쓰레드의 catch(){} 블록에 전달해야 함

package ThreadgetState;

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 ThreadgetState {
	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());
		
		//TIMED_WAITING
		myThread.interrupt();
		//인터럽트 준비 시간
		try {Thread.sleep(100);} catch(InterruptedException e) {}
		System.out.println("MyThread State ="+myThread.getState());
	}
}

 

2) join(long millis)를 이용한 일시정지 및 RUNNABLE 상태 전환

 

- 특정 쓰레드 객체의 join(long millis)메서드를 호출하면 해당 쓰레드 객체에게 매개변수로 넘겨 준 시간 동안 CPU 사용권을 넘겨 주고, 자신은 일시정지 상태가 됨

- myThread2는 내부에 myThread1 타입의 필드를 갖고 있으며, 객체를 생성할 때 생성자의 매개변수로 넘겨 받아 이 필드를 초기화 

 - myThread2 클래스의 run() 메서드에서는 시작과 동시에 myThread1.join(3000)을 호출 

  (myThread2 쓰레드를 실행하면 myThread1을 3초 동안 먼저 실행)

 - 3초가 지나거나 interrupt()가 발생하면 myThread2는 다시 RUNNABLE상태가 됨

package ThreadgetState;

class MyThread1 extends Thread {
	//override
	public void run() {
		for(long i =0; i<10000000L; i++) {} //시간 지연
	}
}

class MyThread2 extends Thread {
	MyThread1 myThread1;
	public MyThread2(MyThread1 myThread1) {
		this.myThread1 = myThread1;
	}
	//override
	public void run() {
		try {
			myThread1.join(3000); //myThread1에게 최대 3초 동안 CPU 우선 사용권 부여
		}catch(InterruptedException e) {
			System.out.println("--join(..)진행 중 interrupt() 발생");
			for(long i = 0; i < 1000000000L; i++) {} //시간지연
		}
	}
}
public class ThreadgetState {
	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());
		//TIMED_WAITING
		myThread2.interrupt();
		try {Thread.sleep(100);} catch(InterruptedException e) {} //인터럽트 준비 시간
		System.out.println("MyThread1 State="+myThread1.getState());
		System.out.println("MyThread2 State="+myThread2.getState());
		
	}
}

 

4. BLOCKED

 

- 동기화 메서드 또는 동기화 블록을 실행하고자 할 때 이미 다른 쓰레드가 해당 영역을 실행하고 있는 경우 발생

- 해당 동기화 영역이 잠겨 있을 때는 이미 실행하고 있는 쓰레드가 실행을 완료하고, 해당 동기화 영역의 열쇠를 반납할 때까지 기다려야 함 

 - 몇번 째로 BLOKCKED 상태에 빠졌는지는 중요하지 않으며, 열쇠가 반납되는 순간 BLOCKED 상태의 모든 쓰레드가 다시 경쟁해 열쇠를 먼저 차지하는 쓰레드가 실행 (실행 순서와 상관없이 경쟁)

 

- 공유 객체 동기화로 인한 BLOCKED 실행

 -> MyBlockTest 클래스에는 3개의 쓰레드가 익명 이너 클래스로 정의 

 -> startAll() 메서드에서는 내부에 포함된 3개의 쓰레드를 한꺼번에 실행

 ->main() 메서드에서는 단순히 MyBlockTest 객체를 생성한 후 startAll() 메서드를 호출해 내부의 쓰레드를 실행

 

package ThreadgetState;

class MyBlockTest {
	//공유 객체
	MyClass mc = new MyClass();
	
	//3개의 쓰레드 필드 생성
	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 mbt = new MyBlockTest();
		mbt.startAll();
	}
}

 

 

5. WAITING

 

- 일시정지하는 시간의 지정 없이 쓰레드 객체.join() 메서드 호출 

 -> 조인된 쓰레드 객체의 실행이 완료될 때까지 이를 호출한 쓰레드는 WAITING 상태가 됨

 -> 조인한 쓰레드가 완료되거나 interrupt() 메서드로 예외를 인위적으로 발생시켰을 때만 다시 RUNNABLE 상태로 돌아 갈 수 있음

 

- Thread 클래스의 인스턴스 메서드인 wait() 호출

 -> 해당 쓰레드는 WAITING 상태가 됨

 -> wait() 메서드로 WAITING이 된 쓰레드는 다른 쓰레드에서 notify() 또는 notifyAll()을 호출해야만 RUNNABLE 상태가 될 수 있음 (스스로는 WAITING 상태를 벗어날 수 없음!!)

 -> 쓰레드는 실행 중에 wait() 메서드를 만나면 그 자리에서 WAITING 상태가 되고 이후 다른 쓰레드에서 notify() 또는 notifyAll() 메서드가 호출되면 일시정지됐던 지점인 wait()의 다음 줄 부터 다시 이어서 실행 

 -> wait(),notify(),notifyAll() 메서드는 반드시 동기화 블록에서만 사용 가능

 

- 동기화만을 사용했을 떄 임의적인 두 쓰레드의 실행 순서 

 

 -> 데이터 1개만을 갖고 있는 객체를 2개의 쓰레드 (SetThread, GetThread)가 공유 

 -> SetThread에서는 8개의 데이터를 순차적으로 1개씩 넣고, GetThread는 순차적으로 꺼내고자 함

 -> DataBox 클래스의 읽기 메서드 (inputData(int data))와 쓰기 메서드(outpoutData())를 모두 동기화 메서드로 설정 

 -> 읽기 메서드와 쓰기 메서드가 번갈아가면서 실행되는 것이 아니라 자유 경쟁에서 승리한 쓰레드가 랜덤하게 나오게 됨

 

package ThreadgetState;

class DataBox {
	int data;
	synchronized void inputData(int data) {
		this.data = data;
		System.out.println("입력 데이터:"+data);
	}
	synchronized void outputData(int data) {
		System.out.println("출력 데이터:"+data);
	}
}
public class WaitNotify {
	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(i);
				}
			};
		};
		
		t1.start();
		t2.start();
	}
}

 

- wait(), notify()를 이용한 쓰레드의 교차 실행

 

 1) inputData()메서드 

  - 쓰기 쓰레드에서 호출

  - isEmpty 불리언값을 이용해 데이터가 비어 있는지를 검사  

 

  - isEmpty = true

    -> 데이터값이 비어 있는 것으로 판단해 매개 변수로 넘어온 데이터값을 필드에 복사한 후 isEmpty = false로 변경하고, WAITING상태의 읽기 쓰레드를 RUNNABLE 상태로 바꾸기 위한 notify() 메서드를 호출

 

  - isEmpty = false

    -> 쓰기를 완료한 데이터를 아직 읽기 쓰레드가 읽지 않은 것으로 간주해 wait() 메서드를 실행함으로써 자신은 WAITING상태가 됨 

    -> 읽기 쓰레드가 동기화 영역 실행 

 

 2) outputData() 메서드 

  - 읽기 쓰레드에서 호출

 - isEmpty 불리언값을 이용해 데이터가 비어 있는지를 검사 

 

  -isEmpty = true

   -> 아직 읽을 데이터가 없는 것이므로 자신은 열쇠를 반납하고 WAITING 상태가 됨

 

  - isEmpty = false

   -> 읽을 데이터가 있다는 것이므로 해당 데이터를 출력한 후 isEmpty =true로 변환

   -> 쓰기쓰레드를 RUNNABLE 상태로 변환시키기 위한 notify() 메서드를 호출

쓰기 쓰레드 동작(데이터 쓰기)
읽기 쓰레드 깨우기(notify())
쓰기 쓰레드 일시정지 (wait())
읽기 쓰레드 동작(데이터 읽기)
쓰기 쓰레드 꺠우기(notify())
읽기 쓰레드 일시정지(wait())
package ThreadgetState;

class DataBox {
	boolean isEmpty = true;
	int data;
	synchronized void inputData(int data) {
		if(!isEmpty) {
			try {wait();}catch(InterruptedException e) {} //WAITING
		}
		this.data = data;
		isEmpty = false;
		System.out.println("입력 데이터:"+data);
		notify();
	}
	
	synchronized void outputData() {
		if(isEmpty) {
			try {wait();}catch(InterruptedException e) {} //WAITING
		}
		isEmpty=true;
		System.out.println("출력데이터:"+data);
		notify();
	}
}
public class WaitNotify {
	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();
	}
}