스레드 상태
- 스레드 객체를 생성하고, start() 메소드를 호출하면 곧바로 스레드가 실행되는 것처럼 보이지만, 사실은 실행 대기 상태(RUNNABLE)가 된다. 실행 대기 상태란 아직 스케쥴링이 되지 않아서 실행을 기다리고 있는 상태를 말한다.
- 실행 대기 상태에 있는 스레드 중에서 스레드 스케쥴링으로 선택된 스레드가 비로소 CPU를 점유하고 run() 메소드를 실행한다. 이때를 실행(RUNNING) 상태라고 한다. 실행상태의 스레드는 run()메소드를 모두 실행하기 전에 스레드 스케줄링에 의해 다시 실행 대기 상태(RUNNABLE)로 돌아갈 수 있다. 그리고 실행 대기 상태에 있는 다른 스레드가 선택되어 실행 상태가 된다. 이렇게 스레드는 실행 대기 상태와 실행 상태를 번갈아가면서 자신의 run() 메소드를 조금씩 실행한다.
- 실행상태에서 run()메소드가 종료되면, 더 이상 실행할 코드가 없기 때문에 스레드의 실행은 멈추게 된다. 이 상태를 종료 상태(TERMINATED)라고 한다.
- 스레드 객체 생성(NEW)
- 실행 대기(RUNNABLE)
- 실행(RUNNING)
- 종료(TERMINATED)
-
=> 경우에 따라서 스레드는 실행 상태에서 실행 대기 상태로 가지 않을 수도 있다. 실행 상태에서 일시 정지 상태로 가기도 하는데, 일시 정지 상태의 종류는 WAITING, TIMED_WAITING, BLOCKED가 있다. 일시정지 상태의 스레드가 다시 실행되려면 우선 실행 대기 상태로 돌아가야 한다.
-
이러한 스레드의 상태를 코드로 알아볼수 있도록 자바 5부터 Thread 클래스의 getState()메소드가 추가되었고, getState()메소드는 다음 표와같이 열거 상수를 리턴한다.
상태 | 열거 상수 | 설명 |
---|---|---|
객체 생성 | NEW | 스레드 객체가 생성, 아직 start() 메소드가 호출되지 않은 상태 |
실행 대기 | RUNNABLE | 실행 상태로 언제든지 갈 수 있는 상태 |
일시 정지 | WAITING | 다른 스레드가 통지할 때까지 기다리는 상태 |
TIMED_WAITING | 주어진 시간 동안 기다리는 상태 | |
BLOCKED | 사용하고자 하는 객체의 락이 풀릴 때까지 기다리는 상태 | |
종료 | TERMINMATED | 실행을 마친 상태 |
다음 세가지 코드는 스레드의 상태를 볼수 있는 코드 세트이다.
타겟 스레드의 상태를 출력하는 스레드: StatePrintThread
public class StatePrintThread extends Thread{
private Thread targetThread;
public StatePrintThread(Thread targetThread){
this.targetThread = targetThread;
}
@Override
public void run() {
while(true){
Thread.State state = targetThread.getState();
System.out.println("타겟 스레드 상태: " +state);
if(state == State.NEW){
targetThread.start();
}
if(state == State.TERMINATED){
break;
}
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
타겟 스레드
// NEW -> RUNNABLE -> TIMED_WAITING -> RUNNABLE -> TERMINATED
public class TargetThread extends Thread{
@Override
public void run() {
//RUNNABLE 상태유지(-><- RUNNING)
for(int i =0; i<2000000000; i++){}
try {
sleep(1500);//1.5초간 스레드 일시 정지
}catch (InterruptedException e){
e.printStackTrace();
}
//RUNNABLE 상태유지(-><- RUNNING)
for(long i =0; i<2000000000; i++){}
}
}
실행 클래스
public class Main {
public static void main(String[] args) {
Thread targetThread = new TargetThread();
Thread statePrintThread = new StatePrintThread(targetThread);
statePrintThread.start();
}
}
/*실행상태
타겟 스레드 상태: NEW
타겟 스레드 상태: RUNNABLE
타겟 스레드 상태: RUNNABLE
타겟 스레드 상태: RUNNABLE
타겟 스레드 상태: TIMED_WAITING
타겟 스레드 상태: TIMED_WAITING
타겟 스레드 상태: TIMED_WAITING
타겟 스레드 상태: RUNNABLE
타겟 스레드 상태: RUNNABLE
타겟 스레드 상태: TERMINATED
*/
스레드 상태 제어
- 사용자는 미디어 플레이어로 동영상을 보다가 일시 정지시킬 수도 있고, 종료시킬 수도 있다. (1) 일시 정지는 조금 후 다시 동영상을 재생시킨다는 의미이므로 미디어 플레이어는 동영상 스레드를 일시 정지 상태로 만들어야 한다. (2)그리고 종료는 더 이상 동영상을 보지 않겠다는 의미이므로 미디어 플레이어는 스레드를 종료상태로 만들어야 한다.
- 이와 같이 실행 중인 스레드의 상태를 변경하는 것을 스레드 상태 제어라고 한다.
=> 다음 표는 스레드의 상태 변화를 가져오는 메소드의 종류를 보여준다.(Thread 클래스, Object 클래스의 메소드들)
메소드 | 설명 |
---|---|
interrupt() | 일시 정지 상태의 스레드에서 InterruptedException 예외를 발생시켜, |
예외 처리 코드(catch)에서 실행 대기 상태로 가거나 종료 상태로 갈 수 있도록 한다. | |
notify(), notifyAll() | 동기화 블록 내에서 wait() 메소드에 의해 일시 정지 상태에 있는 스레드를 실행 대기 상태로 만든다. |
sleep(long millis), sleep(long millis, int nanos) | 주어진 시간 동안 스레드를 일시 정지 상태로 만든다. 주어진 시간이 지나면 자동적으로 실행 대기 상태가 된다. |
join(),join(long millis),join(long millis, int nanos) | join() 메소드를 호출한 스레드는 일시 정지 상태가 된다. 실행 대기 상태로 가려면 join()메소드를 멤버로 가지는 스레드가 종료되거나, 매개값으로 주어진 시간이 지나야 한다. |
wait(), wait(long millis), wait(long millis, int nanos) | 동기화(synchronized) 블록 내에서 스레드를 일시 정지 상태로 만든다. 매개값으로 주어진 시간이 지나야 한다. |
yield() | 실행 중에 우선순위가 동일한 다른 스레드에게 실행을 양보하고 실행 대기 상태가 된다. |
주어진 시간동안 일시 정지(sleep())
- Thread.sleep() 메소드를 호출한 스레드는 주어진 시간 동안 일시 정지 상태(TIMED_WAITING)가 되고, 다시 실행 대기 상태(RUNNABLE)로 돌아간다.
- 매개값에는 얼마 동안 일시 정지 상태로 있을 것인지, 밀리세컨드(1/1000) 단위로 시간을 주면 된다. 즉 1000을 주면 1초가 된다.
- 일시 정지 상태에서 주어진 시간이 되기 전에 interrupt() 메소드가 호출되면 InterruptedException 예외가 발생하기 때문에 예외 처리가 필요하다.
public class SleepExample {
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i = 0; i<3; i++){
toolkit.beep();
System.out.println("열심히 공부하자");
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
// 실행결과:
// "열심히 공부하자"가 3초마다 출력되고,동시에 비프음이 3초마다 발생한다.
다른 스레드에게 실행 양보(yield()) => 스레드가 처리하는 작업은 반복적인 실행을 위해 for문과 while문을 포함하는 경우가 많다. 가끔 이 반복문들이 무의미한 반복을 하는 경우가 있는데 다음 코드로 이해해보자.
@Override
public void run(){
while(true){
if(work){
System.out.println("ThreadA 작업 내용");
}
}
}
=> 스레드가 시작되어 run() 메소드를 실행하면 while(true){}블록을 무한 반복 실행하는데, 만약 work의 값이 false라면 그리고 work의 값이 false 에서 true로 변경되는 시점이 불명확하다면, while문은 어떠한 실행문도 실행하지 않고 무의미한 반복을 한다.(이것은 엄청난 낭비이다.)
=> 이것보다는 다른 스레드에게 실행을 양보하고 자신은 실행 대기 상태로 가는 것이 전체 프로그램 성능에 도움이 된다. 이는 Thread 클래스의 yield()메소드로 할 수 있다. yield() 메소드를 호출한 스레드는 실행 대기 상태로 돌아가고 동일한 우선순위 또는 높은 우선순위를 갖는 다른 스레드가 실행 기회를 가질 수 있도록 해준다.(즉, 우선순위가 낮은 스레드에게는 권한이 없다는 뜻이다.)
아래 코드는 yield()메소드를 사용하여 무의미한 반복을 피하고 동일한 우선순위나 높은 우선순위를 갖는 스레드에게 실행을 양보(yield)한다.
pulbic void run(){
while(true){
if(work){
System.out.println("ThreadA 작업 내용");
}
else{
Thread.yield(); // 다른 스레드(우선순위가 같거나 높은 스레드)에게 실행 양보
}
}
}
스레드 실행 양보 예제
public class YieldExample {
public static void main(String[] args) {
ThreadC threadC = new ThreadC();
ThreadD threadD = new ThreadD();
threadC.start();
threadD.start();
//메인스레드 3초간 정지시켜 threadC 와 threadD 3초간 번갈아가면서 실행시키기
try{Thread.sleep(3000);} catch (InterruptedException e){}
System.out.println("스레드 C를 정지시킵니다.");
threadC.setWork(false); // false이면 yield()가 호출되어 양보되고 ThreadD만 실행
try{Thread.sleep(3000);} catch (InterruptedException e){}
System.out.println("스레드 C를 다시 실행시킵니다.");
threadC.setWork(true); // true를 넣어 다시 스레드 C와 D 번갈아가면서 실행
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadC.setStop(true); // threadC 종료
threadD.setStop(true);// threadD 종료
}
}
/* 실행내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
스레드 C를 정지시킵니다. => yield()를 통해 스레드 C가 실행을 양보하여 자신은 실행되지 않고 스레드 D가 실행됨을 알 수 있다.
ThreadD 작업 내용
ThreadD 작업 내용
ThreadD 작업 내용
ThreadD 작업 내용
ThreadD 작업 내용
ThreadD 작업 내용
스레드 C를 다시 실행시킵니다. => work 플래그를 다시 true로 하여 C와 D가 번갈아가며 실행됨을 알 수 있다.
ThreadC 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
ThreadC 작업 내용
ThreadD 작업 내용
Thread C 종료
Thread D 종료
*/
ThreadC
public class ThreadC extends Thread {
private boolean stop = false; //종료 플래그(flag)
private boolean work = true; //작업 진행 여부 플래그(flag)
@Override
public void run() {
while(!stop){
if(work) {
System.out.println("ThreadC 작업 내용");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
Thread.yield(); //work가 false가 되면 다른 스레드에게 실행 양보!
}
}
System.out.println("Thread C 종료");
}
void setStop(boolean stop){
this.stop = stop;
}
void setWork(boolean work){
this.work = work;
}
boolean getStop(){
return stop;
}
boolean getWork(){
return work;
}
}
ThreadD
public class ThreadD extends Thread {
private boolean stop = false; //종료 플래그(flag)
private boolean work = true; //작업 진행 여부 플래그(flag)
@Override
public void run() {
while(!stop){
if(work) {
System.out.println("ThreadD 작업 내용");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
Thread.yield(); //work가 false가 되면 다른 스레드에게 실행 양보!
}
}
System.out.println("Thread D 종료");
}
void setStop(boolean stop){
this.stop = stop;
}
void setWork(boolean work){
this.work = work;
}
boolean getStop(){
return stop;
}
boolean getWork(){
return work;
}
}
다른 스레드의 종료를 기다림(join())
=> 스레드는 다른 스레드와 독립적으로 실행하는 것이 기본이지만, 다른 스레드가 종료될때까지 기다렸다가 실행해야 하는 경우가 발생할 수 있다. 예를 들어 계산 작업을 하는 스레드가 모든 계산 작업을 마쳤을 때, 계산 결과값을 받아 이용하는 경우가 그렇다.( 메인스레드에서 계산 작업스레드를 가동시키고 그 계산값을 메인스레드가 출력할때, 출력하는 코드가 계산 작업스레드보다 먼저 호출이 되면 원하는 값이 나오지(보통 0이 나온다.) 않는데, 이 경우 메인 스레드는 계산 작업 스레드가 다 계산을 할때까지 기다려줘야한다.)
=> 이런 경우를 해결하기 위해 자바는 Thread 클래스의 join() 메소드를 제공하고 있는데 한 스레드가 다른 스레드의 join() 메소드를 호출하면 ThreadA는 ThreadB가 종료할 때까지 일시 정지 상태가 된다. ThreadB의 run 메소드()가 종료되면 비로소 ThreadA는 일시 정지에서 풀려 다음 코드를 실행하게 된다.
//ThreadA의 메소드
threadB.start(); //스레드 B를 실행시킴
threadB.join(); // 스레드 B가 다 실행될 때까지 메소드, 즉 스레드 A는 일시 정지 상태가 되고,
// 스레드 B가 다 실행되면 메소드, 즉 스레드 A는 비로소 아래 코드를 실행하게 된다.
...
SumThread
public class SumThread extends Thread{
private long sum;
long getSum(){
return sum;
}
void setSum(long sum){
this.sum = sum;
}
@Override
public void run() {
for(int i=1; i<=100; i++){ // 1부터 100까지 합하는 로직
sum+=i;
}
}
}
join()이 없는 메인 스레드
public class JoinExample {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.start();
System.out.println("실행된 sum 값: " + sumThread.getSum());
}
}
//실행 내용:
//실행된 sum 값: 0
=> join()메소드를 쓰지 않아 메인 스레드가 sumThread의 run()메소드가 다 실행되기도 전에 sumThread,getSum()의 값을 0으로 반환하고 있다. 이는 우리가 원하는 답이 아니다.
join()이 있는 메인 스레드
public class JoinExample {
public static void main(String[] args) {
SumThread sumThread = new SumThread();
sumThread.start();
try {
sumThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("실행된 sum 값: " + sumThread.getSum());
}
}
/* 실행내용
실행된 sum 값: 5050
*/
=> 메인스레드가 sumThread의 join() 메소드를 호출하여(자신이 기다리려면 기다릴 남의 스레드의 join()메소드를 호출해야한다.) sumThread가 계산이 완료될 때까지 일시 정지상태가 된다. 결국 sumThread는 1부터 100까지 합하여 sum 필드는 값이 5050이 되고, 이후 메인 스레드는 다시 실행되어 sumThread.getSum()을 호출하여 제대로된 값이 출력됨을 알수 있다.
=>메인스레드는 SumThread가 계산 작업을 모두 마칠 때까지 일시 정지 상태에 있다가 SumThread가 최종 계산된 결과값을 산출하고 종료하면 결과값을 받아 출력한다.
- 해당 스레드가 남의 스레드를 기다리려면 남의 스레드의 join() 메소드를 호출해야 한다. 헷갈리지 말자.
스레드 간 협업(wait(), notify(), notifyAll())
- 경우에 따라서는 두 개의 스레드를 번갈아가며 실행해야 하는 경우가 있다. 정확한 교대 작업이 필요할 경우 자신의 작업이 끝나면 상대방 스레드를 일시 정지 상태에서 풀어주고, 자신은 일시 정지 상태로 만들면 된다. 따라서 wait()메소드와 notify() 메소드가 필요할 것이다.
-
이 방법의 핵심은 공유 객체에 있다. 공유 객체는 두 스레드가 작업할 내용을 각각 동기화 메소드(synchronized method)로 구분해 놓는다.한 스레드가 작업을 완료하면 (1)notify() 메소드를 호출해서 다른 스레드를 실행 대기 상태로 만들고, (2)자신은 두번 작업을 하지 않도록 wait() 메소드를 호출하여 일시 정지 상태로 만든다.
- wait(long timeout) 이나, wait(long timeout, int nanos)도 있는데 이 메소드들은 notify()를 호출하지 않아도 지정된 시간이 지나면 스레드가 자동적으로 실행 대기 상태가 된다.
-
notify() 메소드와 동일한 역할을 하는 notifyAll() 메소드도 있는데, notify() 메소드는 일시 정지 상태에 있는 스레드들중 하나만 실행 대기 상태로 만들지만, notifyAll() 메소드는 일시 정지 상태에 있는 스레드들 모두 실행 대기 상태에 놓는 특징이 있다.
- 또 wait(), notify(), notifyAll() 메소드는 Thread 클래스의 메소드가 아니라 Object 클래스의 메소드임을 명심하자. 따라서 공유 객체 안에서 wait(), notify()메소드를 사용해서 교대 작업을 구현할 수 있는 것이다.
공유객체인 WorkObject 의 클래스
class WorkObject {
synchronized void methodA(){
System.out.println("ThreadA의 methodA() 작업 실행");
notify(); // 일시 정지 상태에 있는 스레드들 중 하나를 실행 대기 상태로 만듬 -> threadB
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized void methodB(){
System.out.println("ThreadB의 methodB() 작업 실행");
notify(); // 일시 정지 상태에 있는 스레드들 중 하나를 실행 대기 상태로 만든다. -> ThreadA
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ThreadA
public class ThreadA extends Thread{
private WorkObject workObject;
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;
ThreadB(WorkObject workObject){
this.workObject = workObject;
}
@Override
public void run() {
for(int i=0; i<10; i++){
workObject.methodB();
}
}
}
main 함수
public class NotifyExample {
public static void main(String[] args) {
WorkObject workObject = new WorkObject(); //공유 객체 WorkObject.
ThreadA threadA = new ThreadA(workObject);
ThreadB threadB = new ThreadB(workObject);
threadA.start();
threadB.start();
}
}
/* 실행내용
ThreadA의 methodA() 작업 실행
ThreadB의 methodB() 작업 실행
ThreadA의 methodA() 작업 실행
ThreadB의 methodB() 작업 실행
ThreadA의 methodA() 작업 실행
ThreadB의 methodB() 작업 실행
*/
// => 서로 토스하듯, 실행이 차례대로 되고 있다. ( notify(),wait()의 결과물)
- 데이터를 저장하는 스레드(생산자 스레드)가 데이터를 저장하면, 데이터를 소비하는 스레드(소비자 스레드)가 데이터를 읽고 처리하는 교대 작업도 공유객체를 통한 wait(),notify() 메소드를 통해 구현할 수 있다.
공유객체 DataBox
class DataBox <T> {
private T data;
synchronized T getData(){
if(this.data == null){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T returnValue = this.data;
System.out.println("ConsumerThread가 읽은 데이터: " + returnValue);
this.data = null;
notify();
return returnValue;
}
synchronized void setData(T data){
if(this.data!=null){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.data = data;
System.out.println("ProducerThread가 생성한 데이터: "+ this.data);
notify();
}
}
ProducerThread
public class ProducerThread extends Thread{
private DataBox<Integer> dataBox;
ProducerThread(DataBox dataBox){
this.dataBox = dataBox;
}
@Override
public void run() {
for(int i=0;i<3;i++) {
Integer data = i+5;
dataBox.setData(data);
}
}
}
```
ConsumerThread<br>
```java
public class ConsumerThread extends Thread{
private DataBox<Integer> dataBox;
ConsumerThread(DataBox dataBox){
this.dataBox = dataBox;
}
@Override
public void run() {
for(int i=0;i<3;i++) {
Integer a= dataBox.getData();
}
}
}
```
메인 함수<br>
```java
public class WaitNotifyExample {
public static void main(String[] args) {
DataBox dataBox = new DataBox();
ConsumerThread consumerThread = new ConsumerThread(dataBox);
ProducerThread producerThread = new ProducerThread(dataBox);
consumerThread.start();
producerThread.start();
}
}
/* 실행내용
ProducerThread가 생성한 데이터: 5
ConsumerThread가 읽은 데이터: 5
ProducerThread가 생성한 데이터: 6
ConsumerThread가 읽은 데이터: 6
ProducerThread가 생성한 데이터: 7
ConsumerThread가 읽은 데이터: 7
*/
```
> 스레드의 안전한 종료 ( stop 플래그, interrupt())
1. stop 플래그(flag)를 이용하는 방법
PrintThread1 <br>
```java
public class PrintThread1 extends Thread {
private boolean stop;
void setStop(boolean stop){
this.stop = stop;
}
@Override
public void run() {
while(!stop){
System.out.println("실행중");
}
System.out.println("자원정리");
System.out.println("실행종료");
}
}
```
StopFlagExample <br>
```java
public class StopFlagExample {
public static void main(String[] args) {
PrintThread1 printThread1 = new PrintThread1();
printThread1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
printThread1.setStop(true);
}
}
```
2. interrupt() 메소드를 이용하는 방법
PrintThread2 <br>
```java
public class PrintThread2 extends Thread {
@Override
public void run() {
do {
System.out.println("실행중");
} while (!isInterrupted());
System.out.println("자원정리");
System.out.println("실행종료");
}
}
```
InterruptExample<br>
```java
public class InterruptExample {
public static void main(String[] args) {
PrintThread2 printThread2 = new PrintThread2();
printThread2.start();
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
printThread2.interrupt();
}
}
```
### 데몬 스레드
* 데몬(daemon) 스레드는 주 스레드의 작업을 돕는 보조적인 역할을 수행하는 스레드이다.
* 주 스레드가 종료되면 데몬 스레드는 강제적으로 자동 종료되는데, 그 이유는 주 스레드의 보조 역할을 수행하므로 주 스레드가 종료되면 데몬 스레드의 존재의미가 없어지기 때문이다.
* 데몬 스레드는 일반 스레드와 크게 차이가 없는데, 데몬 스레드의 적용 예는 워드프로세서의 자동 저장, 미디어 플레이어의 동영상 및 음악 재생, 가비지 컬렉터 등이 있는데, 이 기능들은 주 스레드(워드프로세스, 미디어 플레이어, JVM )가 종료되면 같이 종료된다.
```java
public static void main(String[] args){
AutoSaveThread thread = new AutoSaveThread();
thread.setDaemon(true);
thread.start();
}
```
* 주의할 점: start() 메소드 호출하고 난뒤 setDaemon(true)를 호출하면 IllegalThreadStateException이 발생하기 때문에 start() 메소드 호출 전에 setDaemon(true)를 호출하자.
데몬 스레드 예 <br>
```java
public class AutoSaveThread extends Thread{
private void save(){
System.out.println("작업 내용을 저장함.");
}
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
save();
}
}
}
```
메인 스레드<br>
```java
public class DaemonExample {
public static void main(String[] args) {
AutoSaveThread autoSaveThread = new AutoSaveThread();
autoSaveThread.setDaemon(true);
autoSaveThread.start();
try { Thread.sleep(3000); } catch (InterruptedException ignored) { }
System.out.println("메인 스레드 종료");
}
}
```
### 스레드 그룹
* 현재 스레드가 속한 스레드 그룹의 이름을 얻고 싶다면 다음과 같은 코드를 사용할 수 있다.
```java
ThreadGroup threadgroup = Thread.currentThread().getThreadGroup();
String groupName = group.getName();
```
* Thread의 정적 메소드인 getAllStackTrace()을 이용하면 프로세스 내에서 실행하는 모든 스레드에 대한 정보를 얻을 수 있다.
```java
Map<Thread,StackTraceElement[]> map = Thread.getAllStackTraces();
```
현재 실행중인 스레드 정보<br>
```java
public class ThreadInfoExample {
public static void main(String[] args) {
AutoSaveThread autoSaveThread = new AutoSaveThread();
autoSaveThread.setName("AutoSaveThread");
autoSaveThread.setDaemon(true);
autoSaveThread.start();
Map<Thread,StackTraceElement[]> map = Thread.getAllStackTraces();
Set<Thread> threads = map.keySet();
for(Thread thread : threads){
System.out.println("Name: " + thread.getName() + (thread.isDaemon()?"(데몬)": "(주)"));
System.out.println("\t" +"소속그룹: " + thread.getThreadGroup().getName());
System.out.println();
}
}
}
```
> 스레드 그룹 생성
* 명시적으로 스레드 그룹을 만드록 싶다면 다음 생성자 중 하나를 이용해서 ThreadGroup 객체를 만들면 되는데, ThreadGroup 이름만 주거나, 부모 ThreadGroup과 이름을 매개값으로 줄 수 있다.
```java
ThreadGroup tg = new ThreadGroup(String name);
ThreadGroup tg = new ThreadGroup(ThreadGroup parent, String name);
-
스레드 그룹 생성 시 부모(parent) 스레드 그룹을 지정하지 않으면 현재 스레드가 속한 그룹의 하위 그룹으로 생성된다. 예를 들어 main스레드가 ThreadGroup(String name) 을 이용해서 새로운 스레드 그룹을 생성하면, main 스레드 그룹의 하위 스레드 그룹이 된다.
-
새로운 스레드 그룹을 생성한 후 , 이 그릅에 스레드를 포함시키려면 Thread 객체를 생성할때 생성자 매개값으로 스레드 그룹을 지정하면 된다. 유형은 다음과 같이 4가지가 있다.
Thread t = new Thread(ThreadGroup group, Runnable target);
Thread t = new Thread(ThreadGroup group, Runnable target, String name);
Thread t = new Thread(ThreadGroup group, Runnable target, String name, long stackSize);
Thread t = new Thread(ThreadGroup group, Runnable target, String name);
=> Runnable 타입의 target은 Runnable 구현 객체를 말하며, String 타입의 name은 스레드의 이름이다. long 타입의 stackSize는 JVM이 이 스레드에 할당할 stack 크기이다.
스레드 그룹의 일괄 interrupt()
=> 스레드를 스레드 그룹에 포함시키면 얻는 이점: 스레드 그룹에서 제공하는 interrupt()메소드를 이용하면 그룹 내에 포함된 모든 스레드들을 일괄 interrupt 할 수 있다. 이것이 가능한 이유는 스레드 그룹의 interrupt() 메소드는 포함된 모든 스레드의 interrupt() 메소드를 내부적으로 호출해주기 때문이다.
=> 스레드 그룹의 interrupt() 메소드는 소속된 스레드의 interrupt() 메소드를 호출만 할 뿐 개별 스레드에서 발생하는 InterruptException에 대한 예외처리를 하지 않기 때문에 안전한 종료를 위해서는 개별 스레드가 예외 처리를 해야 한다.
InterruptedException이 발생할 때 스레드가 종료되도록 함
public class WorkThread extends Thread {
WorkThread(ThreadGroup threadGroup, String threadName){
super(threadGroup,threadName);
}
@Override
public void run() {
while (true){
System.out.println(getName()+"가 실행됨");
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println(getName() + " interrupted");
break; // while문 빠져나와 스레드 종료시킨다.
}
}
System.out.println(getName() + " 종료됨.");
}
}
스레드 그룹을 이용한 일괄 종료 예제
public class ThreadGroupExample {
public static void main(String[] args) {
ThreadGroup myGroup = new ThreadGroup("myGroup");
WorkThread workThreadA = new WorkThread(myGroup,"workThreadA");
WorkThread workThreadB = new WorkThread(myGroup,"workThreadB");
workThreadA.start();
workThreadB.start();
System.out.println("[ main 스레드 그룹의 list() 메소드 출력 내용 ]");
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
mainGroup.list();
System.out.println();
try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("[myGroup 스레드 그룹의 interrupt() 메소드 호출]");
myGroup.interrupt(); // myGroup 스레드 그룹에 포함된 workThreadA, workThreadB가 모두 interrupt된다.
}
}
/*실행 결과
[ main 스레드 그룹의 list() 메소드 출력 내용 ]
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
java.lang.ThreadGroup[name=myGroup,maxpri=10]
Thread[workThreadA,5,myGroup]
Thread[workThreadB,5,myGroup]
workThreadB가 실행됨
workThreadA가 실행됨
workThreadB가 실행됨
workThreadA가 실행됨
workThreadA가 실행됨
workThreadB가 실행됨
[myGroup 스레드 그룹의 interrupt() 메소드 호출]
workThreadA interrupted
workThreadB interrupted
workThreadA 종료됨.
workThreadB 종료됨.
*/
스레드 풀
execute() 메소드로 작업 처리 요청한 경우
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
//ExecuterService 인터페이스의 execute 메소드 와 submit 메소드
public class ExecuteExample {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for(int i=0; i<10; i++){
Runnable runnable = new Runnable() {
@Override
public void run() {
//스레드 총 개수 및 작업 스레드 이름 출력
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor)executorService;
int poolSize = threadPoolExecutor.getPoolSize();
String threadName = Thread.currentThread().getName();
System.out.println("[총 스레드 개수: " +poolSize +"] 작업 스레드 이름: " +threadName);
//예외 발생
int value = Integer.parseInt("삼");
}
};
//executorService.execute(runnable);
executorService.submit(runnable);
Thread.sleep(1000);// 콘솔에 출력시간을 주기 위해 0.01초 일시 정지시킴
}
executorService.shutdown();
}
}
리턴값이 없는 작업 완료 통보
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class NoResultExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool
(Runtime.getRuntime().availableProcessors());
System.out.println("[작업 처리 요청]");
Runnable runnable = () -> {
int sum =0;
for(int i=1; i<10; i++){sum +=i;}
System.out.println("[처리 결과] " +sum);
};
Future future = executorService.submit(runnable);
try {
System.out.println(future.get());
System.out.println("[작업 처리 완료]");
} catch (InterruptedException | ExecutionException e) {
System.out.println("[실행 예외 발생함] " + e.getMessage());
}
executorService.shutdown();
}
}
/* 실행결과:
[작업 처리 요청]
[처리 결과] 55
[작업 처리 완료]
*/
리턴값이 있는 작업 완료 통보
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ResultByCallableExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
System.out.println("[작업 처리 요청]");
Callable<Integer> task = () -> {
int sum = 0;
for(int i=0; i<10;i++){
sum +=i;
}
return sum;
};
Future<Integer> future = executorService.submit(task);
try {
int sum = future.get();
System.out.println("[처리 결과] " + sum);
System.out.println("[작업 처리 완료]");
}catch (Exception e){
System.out.println("[Runtime Exception 발생함] " + e.getMessage());
}
executorService.shutdown();
}
}
작업 처리 결과를 외부 객체에 저장
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ResultByRunnableExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
System.out.println("[작업 처리 요청]");
class Task implements Runnable{
private Result result;
private Task(Result result){
this.result = result;
}
@Override
public void run() {
int sum=0;
for(int i=1; i<=10; i++)
sum +=i;
System.out.println("지금 누적된 결과: "+sum);
result.addValue(sum);
}
}
Result result = new Result();
Task task1 = new Task(result);
Future<Result> future1 = executorService.submit(task1,result);
try {
result = future1.get();
System.out.println("accumValue: "+ result.accumValue);
}catch (Exception e){
System.out.println("[실행 예외 발생: "+ e.getMessage() +"]");
}
Task task2 = new Task(result);
Future<Result> future2 = executorService.submit(task2,result);
try {
result = future2.get();
System.out.println("accumValue: "+ result.accumValue);
}catch (Exception e){
System.out.println("[실행 예외 발생: "+ e.getMessage() +"]");
}
executorService.shutdown();
}
}
//처리 결과를 저장하는 Result 클래스
class Result{
int accumValue;
synchronized void addValue(int value){
accumValue+=value;
}
}
콜백 방식의 작업 완료 통보받기
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//콜백 방식의 작업 완료 통보받기
public class CallbackExample {
private ExecutorService executorService;
private CallbackExample(){
executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
}
private CompletionHandler<Integer,Void> callback =
new CompletionHandler<>() {
@Override
public void completed(Integer result, Void attachment) {
System.out.println("completed() 실행: " + result);
}
@Override
public void failed(Throwable exc, Void attachment) {
System.out.println("failed() 실행: " + exc.toString());
}
};
private void doWork(final String x, final String y){
Runnable task = () -> {
try {
int intX = Integer.parseInt(x);
int intY = Integer.parseInt(y);
int result = intX +intY;
callback.completed(result, null);
}catch (NumberFormatException e){
callback.failed(e, null);
}
};
executorService.submit(task);
}
private void finish(){
executorService.shutdown();
}
public static void main(String[] args) {
CallbackExample example = new CallbackExample();
//completed
example.doWork("3","3");
//failed
example.doWork("4","사");
example.finish();
}
}