[이것이자바다] chapter 12. 멀티 스레드(Multi Thread) 2

스레드 상태

  1. 스레드 객체 생성(NEW)
  2. 실행 대기(RUNNABLE)
  3. 실행(RUNNING)
  4. 종료(TERMINATED)
상태 열거 상수 설명
객체 생성 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
*/

스레드 상태 제어

=> 다음 표는 스레드의 상태 변화를 가져오는 메소드의 종류를 보여준다.(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())


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가 최종 계산된 결과값을 산출하고 종료하면 결과값을 받아 출력한다.

스레드 간 협업(wait(), notify(), notifyAll())

공유객체인 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()의 결과물)

공유객체 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);
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();
    }
}