쓰레드는 객체가 생성, 실행, 종료되기까지 다양한 상태를 가진다.
각 쓰레드는 Thread.State 상태로 정의되었다.
Thread의 인스턴스 메서드인 getState()로 쓰레드의 상태값을 가져올 수 있다.
Thread.State getState()
Thread.State의 내부에는 6개의 문자열 상수(NEW, RUNNABLE, TERMINATED, TIMED_WAITING, BLOCKED, WAITING)가 저장되어 있다.
NEW | new 키워드로 쓰레드의 객체가 생성된 상태(start()전) |
RUNNABLE | start() 이후 CPU를 사용할 수 있는 상태 다른 쓰레드들과 동시성에 따라 실행과 실행 대기를 교차함 |
TERMINATED | run()메서드의 작업 내용이 모두 완료돼 쓰레드가 종료된 상태 (한 번 실행(start())된 쓰레드는 재실행이 불가능하며 객체를 새롭게 생성해야 함) |
TIME_WAITING | 일정시간 동안 일시정지 된 상태. 일정시간이 지나거나 중간에 interrupt()메서드가 호출되면 다시 RUNNABLE상태가 됨 |
BLOCKED | 동기화 메서드 또는 동기화 블록을 실행하기 위해 먼저 실행 중인 쓰레드의 실행 완료를 기다리는 상태 = 먼저 실행중인 쓰레드가 key를 반납할 때까지 기다리고 있는 상태 쓰레드의 동기화 영역 수행이 완료되면 BLOCKED상태의 쓰레드는 RUNNABLE상태가 돼 해당 동기화 영역을 실행하게 된다. |
WAITIGN | 일정시간이 지정되지 않은 정지 상태즉, 한없이 일시정지된 상태 |
NEW, RUNNABLE, TERMINATED
쓰레드 상태 (NEW, RUNNABLE, TERMINATED) 실습 예제
public class ThreadStateTest1 {
public static void main(String[] args) {
// 쓰레드 상태 저장 클래스
Thread.State state;
// 1. 객체 생성 (NEW)
Thread myThread = new Thread() {
@Override
public void run() {
for(long i=0; i<1000000000L ; i++) {} //시간지연하기 위한 반복문
}
};
state = myThread.getState();
System.out.println("myThread state = "+ state); // NEW 상태
// 2. myThread 시작
myThread.start();
state = myThread.getState();
System.out.println("myThread state = "+ state); //Runnable 상태
// 3. myThread 종료
try {
myThread.join();
} catch (InterruptedException e) { }
state = myThread.getState();
System.out.println("myThread state = "+ state); //TERMINATED 상태
}
}
/*결과
myThread state = NEW
myThread state = RUNNABLE
myThread state = TERMINATED
*/
.join은 해당 쓰레드가 완료될 때까지 mian쓰레드는 기다리겠다는 의미.
여기에서 myThread객체의 실행이 완전히 종료된 시점에 getState()메서드를 호출하기 위해 사용하였음.
RUNNABLE상태에서 다른 쓰레드에게 CPU를 인위로 양보하여 실행대기 상태로 전환할 수 있음
static void Thread.yield();
자신의 차례를 딱 한 번 양보하며 자신의 차례가 다시 돌아오면 CPU를 사용할 수 있다.
RUNNABLE상태에서 yield()메서드를 이용한 CPU사용양보
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<1000000000L ; 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();
// 1. 6초 지연 (1초마다 한번씩 양보)
for(int i=0; i<6; i++) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
thread1.yieldFlag=!thread1.yieldFlag;
thread2.yieldFlag=!thread2.yieldFlag;
}
}
}
결과
thread1 실행
thread1 실행
thread2 실행
thread2 실행
thread2 실행
thread1 실행
thread1 실행
thread1 실행
thread2 실행
thread2 실행
...
TIMED_WAITING
RUNNABLE상태에서 TIMED_WAITING 상태로 전환하는 방법은 두 가지가 있음
1. Thread.sleep()
static void Thread.sleep(long times)
2. join
synchronized void join(long times)
반대로 TIMED_WAITING상태에서 RUNNABLE상태로 전환하는 방법도 두 가지가 있음
1. 일시정지 시간이 종료
2. inturrupt()
void inturrupt()
Thread.sleep(long times)를 이용한 일시정지, RUNNABLE 상태 전환
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()); //TIMED_WAITING
myThread.interrupt(); // TIME_WAITING -> RUNNABLE 상태 전환
try {Thread.sleep(100);} catch (InterruptedException e) {} //인터럽트 준비시간
System.out.println("MyThread State = " + myThread.getState()); //RUNNABLE
}
}
결과
MyThread State = TIMED_WAITING
-- sleep() 진행중 interrupt() 발생
MyThread State = RUNNABLE
join(long times)를 이용한 일시정지 및 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); //myThread1에게 최대 3초당안 CPU 우선 사용권 부여
} 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) {
//#1. 객체 생성
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()); //RUNNABLE
System.out.println("MyThread2 State = " + myThread2.getState()); //TIMED_WAITING
myThread2.interrupt(); //TIMED_WAITING -> RUNNABLE 상태 전환
try {Thread.sleep(100);} catch (InterruptedException e) {} //인터럽트 준비 시간
System.out.println("MyThread1 State = " + myThread1.getState()); //RUNNABLE
System.out.println("MyThread2 State = " + myThread2.getState()); //RUNNABLE
}
}
결과
MyThread1 State = RUNNABLE
MyThread2 State = TIMED_WAITING
-- join(...) 진행중 interrupt() 발생
MyThread1 State = RUNNABLE
MyThread2 State = RUNNABLE
BLOCKED
- BLOCKED는 동기화 메서드 또는 동기화 블록을 실행하고자 할 때 이미 다른 쓰레드가 해당 영역을 실행하고 있는 경우 발생
- 해당 동기화 영역이 잠겼을 때는 이미 실행하고 있는 스레드가 실행을 완료하고, 해당 동기화 영역의 key를 반납할 때까지 기다려야 하는 상태
- t1쓰레드 실행 완료 후 key를 동기화 영역에 반납하는데 t2, t3, t4가 key를 가지기 위해 경쟁을 한다. 즉, 먼저 도착하는 쓰레드가 실행권한을 가지게 된다.
class MyBlockTest {
//1. 공유객체
MyClass mc = new MyClass();
//2. 4 개의 쓰레드 필드 생성
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();
};
};
Thread t4 = new Thread("thread4") {
public void run() {
mc.syncMethod();
};
};
void startAll() {
t1.start();
t2.start();
t3.start();
t4.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());
System.out.println("thread4->" +t4.getState());
for(long i=0; i<1000000000L ; i++) {} //시간지연
}
}
}
public class BlockedState {
public static void main(String[] args) {
MyBlockTest mbt = new MyBlockTest();
mbt.startAll();
}
}
결과
====thread1====
thread1->RUNNABLE
thread2->BLOCKED
thread3->BLOCKED
thread4->BLOCKED
====thread3====
thread1->TERMINATED
thread2->BLOCKED
thread3->RUNNABLE
thread4->BLOCKED
====thread4====
thread1->TERMINATED
thread2->BLOCKED
thread3->TERMINATED
thread4->RUNNABLE
====thread2====
thread1->TERMINATED
thread2->RUNNABLE
thread3->TERMINATED
thread4->TERMINATED
WAITING
- 일시정지하는 시간의 지정없이 쓰레드 객체 .join()메서드를 호출하면 조인된 쓰레드 객체의 실행이 완료될 때까지 이를 호출한 쓰레드는 WAITING 상태가 된다.
- 조인된 쓰레드가 완료되거나 inturrupt()메서드 호출로 예외를 인위적으로 발생시켰을 때만 다시 RUNNABLE상태로 돌아갈 수 있다.
- wait()메서드로 WAITING된 쓰레드는 다른 쓰레드에서 notify(), notifyAll()을 호출해야만 RUNNABLE 상태가 될 수 있다. (스스로 WAITING상태를 벗어날 수 없음)
- WAITING된 쓰레드는 일시정지했던 지점부터 다시 이어서 실행된다.
- wait(), notify(), notifyAll()은 반드시 동기화 블록에서만 사용할 수 있다.
동기화만 사용했을 때 임의적인 두 쓰레드의 실행 순서
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();
}
}
결과
입력데이터 : 1
입력데이터 : 2
입력데이터 : 3
입력데이터 : 4
입력데이터 : 5
입력데이터 : 6
입력데이터 : 7
입력데이터 : 8
출력데이터 : 8
출력데이터 : 8
출력데이터 : 8
출력데이터 : 8
출력데이터 : 8
출력데이터 : 8
출력데이터 : 8
출력데이터 : 8
- 쓰기 쓰레드가 동기화 메서드 실행 후 key가 반납됨
- 읽기 쓰레드가 실행되지 않고 key를 얻기위한 쓰레드는 쓰기와 읽기 쓰레드임
- 매번 호출할 때마다 key를 얻기 위해 경쟁함
- 번갈아가면서 출력되는것이 아닌 자유 경쟁에서 승리한 쓰레드가 랜덤하게 나옴
어떻게 해결하면 좋을까?
먼저, wait(), notify()메서드를 이용하여 쓰레드를 일시정지 및 호출하며 boolean변수를 이용해 쓰기 동작을 할 것인지 읽기 동작을 할 것인지 순서를 정하면 된다.
아래와 같은 방법으로 작동할 것이다.
- 쓰게쓰레드 동작(데이터 쓰기)
- 읽기 쓰레드 깨우기(notify())
- 쓰기 쓰레드 일시정지(wait())
- 읽기 쓰레드 동작(데이터 읽기)
- 쓰기 쓰레드 깨우기(notify())
- 읽기 쓰레드 일시정지(wait())
- 1~6과정 반복
wait(), notify()를 이용한 쓰레드의 교차 실행
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 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();
}
}
결과
입력데이터 : 1
출력데이터 : 1
입력데이터 : 2
출력데이터 : 2
입력데이터 : 3
출력데이터 : 3
입력데이터 : 4
출력데이터 : 4
입력데이터 : 5
출력데이터 : 5
입력데이터 : 6
출력데이터 : 6
입력데이터 : 7
출력데이터 : 7
입력데이터 : 8
출력데이터 : 8
'Language > Java' 카테고리의 다른 글
[Java] 매개변수의 다형성 (0) | 2022.06.10 |
---|---|
[Java] 제네릭(Generic) (0) | 2022.04.08 |
[Java] 쓰레드의 동기화 (0) | 2022.04.06 |
[Java] 쓰레드의 속성 (0) | 2022.04.05 |
[Java] 쓰레드, 멀티쓰레드 (0) | 2022.04.05 |