쓰레드의 상태
- 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();
}
}
'Java > 문법' 카테고리의 다른 글
16 -2 장 제네릭의 문법 (0) | 2023.06.15 |
---|---|
16 -1장 제네릭 클래스와 제네릭 인터페이스 (0) | 2023.06.15 |
10 - 6장 최상위 클래스 Object (0) | 2023.06.01 |
10 - 5장 super 키워드와 super() 메서드 (0) | 2023.06.01 |
10-4장 인스턴스 필드와 정적 멤버의 중복 (0) | 2023.06.01 |