SlideShare uma empresa Scribd logo
1 de 71
Multi-Thread 
Producer-Consumer pattern
Thread를 알아야 하는 이유?
한발 더 나아가 미래를 내다보는 개발자는 이미 단일 컴퓨터 
내부에서의 병렬 처리를 넘어 여러 대의 컴퓨터에서 병렬로 
동작하는 플랫폼을 찾고 있고, 이와 같은 분산 병렬 처리 플랫 
폼이 한창 인기를 얻는 요즘입니다. 이런 대규모 병렬 처리 플 
랫홈도 중요하긴 하지만, 그 안에서는 항상 단일 프로세스 내 
부에서 동작하는 여러 스레드가 안정적으로 실행되도록 하는 
병렬 처리 기법이 적용되어 있음을 잊어서는 안될 것입니다. 
-’멀티 코어를 100% 활용하는 자바 병렬 프로그래밍’ 옮김이의 말 중
Thread를 판단하는 기준??
1. Safety 
2. Liveness 
3. Performance 
4. Reuse
1. Safety
Thread에서 사용되는 값들의 무결성 
정확성!! 
 Class가 해당 Class 명세에 부합한다. 
해당 객체가 어떻게 생성되고 어떻게 사용되든 다른 작업 없이, 
정확하게 동작하면 해당 객체는 Thread Safe 하다고 말한다.
2. Liveness
원하는 일이 원하는 시기에 동작한다. 
Deadlock 
 서로 다른 Thread가 서로 가지고 있는 독점 자원을 원하여 멈춰져 있는 현상 
Starvation 
 Thread가 자신이 원하는 자원을 할당받지 못하는 상황 
Livelock 
 서로 다른 Thread가 서로의 상태를 변경하면서 작업을 진행하지 못하는 현상
3. Performance
Muti-Thread 환경은 Single-Thread 환경보다 성능상 자원을 더 소모한다. 
(락, 메모리, 상태 설정, Thread 생성 및 제거 등등) 
Muti-thread는 결국!! 
이런 비용을 지불하여 응답성과 처리용량을 늘리는 것이 목적!!! 
서로 다른 Thread의 경쟁할 일이 없는 synchonized 블록의 경우 JVM 수준에서 동 
기화 관련 부하를 줄이거나 아예 없애주기 때문에 매우 빠르게 동작한다. 실행 순 
서를 고정하는 역할을 할 수도 있다. 
대신 경쟁조건이 존재하는 경우 대부분의 JVM이 운영체제의 기능을 호출한다.
4. Reuse
Synchonized 키워드, volatile 키워드 
• 성능을 위해서는 위에 두 키워드 뿐 아니라 Multi-thread의 기법에 들 
어가는 모든 기술을 최소화하는 것이 좋다. 
하지만!! 이런 키워드가 없는 경우 
• 후임 개발자가 해당 Class에 대해 Thread safe 여부를 판단하기 힘들고 
더 심한 경우에는 이미 안전한 Class를 망칠 수도 있다.
Synchonized 키워드를 사용하지 않아도 되는 Class
public final class Person { 
private final String name; 
public Person(String name){ 
this.name = name; 
} 
public String getName(){ 
return name; 
} 
public String toString(){ 
return "Person :: name = "+name; 
} 
} 
public class SyncPerson { 
private String name; 
public SyncPerson(String name){ 
this.name = name; 
} 
public synchronized String getName(){ 
return name; 
} 
public synchronized void setName(String name){ 
this.name = name; 
} 
public synchronized String toString(){ 
return "Person :: name = " + name; 
} 
} 
for(long i=0;i<CALL_COUNT;i++){ 
Person person 
= new Person(String.valueOf(i)); 
person.toString(); 
} 
SyncPerson syncPerson 
= new SyncPerson(String.valueOf(0)); 
for(long i=0;i<CALL_COUNT;i++){ 
syncPerson.setName(String.valueOf(i)); 
syncPerson.toString(); 
} 
Person : BEGIN 
Person : END 
time = 475msec. 
SyncPerson : BEGIN 
SyncPerson : END 
time = 523msec.
public class TestMain { 
private static final long CALL_COUNT = 10000000L; 
public static void main(String args []){ 
new PersonConsumerThread("Single Thread",CALL_COUNT).run(); 
Thread t1 = new PersonConsumerThread("Thread-0", CALL_COUNT/2); 
t1.start(); 
Thread t2 = new PersonConsumerThread("Thread-1", CALL_COUNT/2); 
t2.start(); 
//대기 
try { t1.join(); t2.join(); } catch(InterruptedException e) { } 
SyncPerson syncPerson = new SyncPerson(String.valueOf(0)); 
new SyncPersonCounmerThread("Single Thread", CALL_COUNT, syncPerson).run(); 
Thread t4 = new SyncPersonCounmerThread("Thread-4", CALL_COUNT/2, syncPerson); 
t4.start(); 
Thread t5 = new SyncPersonCounmerThread("Thread-5", CALL_COUNT/2, syncPerson); 
t5.start(); 
} 
} 
Single Thread Person : BEGIN 10000000 
Single Thread Person : END time = 534msec. 
Main Person : BEGIN 
Thread-0 Person : BEGIN 5000000 
Thread-1 Person : BEGIN 5000000 
Thread-0 Person : END time = 297msec. 
Thread-1 Person : END time = 298msec. 
Main Person : END time = 298msec. 
Single Thread SyncPerson : BEGIN 10000000 
Single Thread SyncPerson : END time = 668msec. 
Main SyncPerson : BEGIN 
Thread-4 SyncPerson : BEGIN 5000000 
Thread-5 SyncPerson : BEGIN 5000000 
Thread-4 SyncPerson : END time = 1110msec. 
Thread-5 SyncPerson : END time = 1121msec. 
Main SyncPerson : END time = 1790msec.
Immutable??? 
public static void main(String args[]){ 
Person person = new Person(“bob”); 
…… 
System.out.println(person.getName()); 
System.out.println(person.getName().replace(“b”,”p”)); 
System.out.println(person.getName()); 
} 
bob 
pop 
bob 
String의 replace()를 사용하면 실제 객체 값에는 영향을 
주지 않고 그 순간만 데이터를 바꿔서 사용할 수 있다.
String에 replace() 같은 메소드가 문제! 
다행히 String에서는 별 문제 없지만…
public final class Person { 
private final StringBuffer info; 
public Person(String name){ 
this.info 
= new StringBuffer("name = “ 
+ name); 
} 
public StringBuffer getInfo(){ 
return info; 
} 
public String toString(){ 
return "Person :: " + info; 
} 
} 
public static void main(String args[]){ 
Person person = new Person("bob"); 
System.out.println(person); 
StringBuffer name 
= person.getInfo(); 
name.replace(7,10,"pop"); 
System.out.println(person); 
} 
Person :: name = bob 
Person :: name = pop 
StringBuffer에서 제공하는 replace()는 String과 달리 
객체에 직접 영향을 주기 때문에 Muti-thread 환경에서 
안전하지 못하다.
Thread 종료
Thread의 종료는 매우 중요하다!! 
• Thread가 정상적으로 종료하지 않으면, 
-> JVM 역시 종료되지 않고 대기하게 된다.
실행 중인 Thread를 그냥 종료??? 
• 작업 중이던 메모리에 대한 반환 작업이 정상적으로 이뤄지지 않는 
경우도 있고 해서 Thread.stop()과 Thread.suspend()와 같은 메소드는 
사용하면 안된다!!!(deprecated) 
JAVA에서는 특정 Thread를 종료시키면 안된다!! 
• 작업을 수행하는 Thread와 해당 Thread의 종료를 요청하는 Thread를 
같이 생각해야 한다.
public class PrimeGenerator implements Runnable{ 
private final List<BigInteger> primes = new ArrayList<BigInteger>(); 
private volatile Boolean cancelled; 
public void run(){ 
BigInteger p = BigInteger.ONE; 
while(!cancelled) { 
p = p.nextProbablePrime(); 
synchronized(this){ 
primes.add(p); 
} 
} 
} 
public void cancel(){ 
cancelled = true; 
} 
} 
해당 cancel 메소드를 호출하면 해당 Thread는 작업을 
정리한 후에 종료한다.
해당 Thread가 sleep이나 join같은 대기 상태라면???
public class PrimeGenerator implements Runnable{ 
private final List<BigInteger> primes = new ArrayList<BigInteger>(); 
private volatile Boolean cancelled; 
public void run(){ 
BigInteger p = BigInteger.ONE; 
Thread.sleep(1000000); 
while(!cancelled) { 
p = p.nextProbablePrime(); 
synchronized(this){ 
primes.add(p); 
} 
} 
} 
public void cancel(){ 
cancelled = true; 
} 
} 
해당 Thread는 대기기간이 끝날 때까지 
Cancelled가 아무런 영향도 미치지 못한다.
Interrupted 를 활용하자!! 
public class PrimeGenerator implements Runnable{ 
private final List<BigInteger> primes = new ArrayList<BigInteger>(); 
public void run(){ 
BigInteger p = BigInteger.ONE; 
while(!Thread.currentThread().isInterrupted()) { 
p = p.nextProbablePrime(); 
synchronized(this){ 
primes.add(p); 
} 
} 
} 
}
public class PrimeGenerator implements Runnable{ 
private final List<BigInteger> primes = new ArrayList<BigInteger>(); 
public void run(){ 
BigInteger p = BigInteger.ONE; 
while(!Thread.currentThread().isInterrupted()) { 
try { 
Thread.sleep(5000); 
} catch (InterruptedException e) { } 
p = p.nextProbablePrime(); 
synchronized(this){ 
primes.add(p); 
} 
} 
} 
} 
다른 대기 중이라도 interrupt가 발생하면 대기 상태에서 벗어나게 된다. 
종종 대기 상태인 thread를 깨우기 위해 interrupt를 사용하는 경우 두 
interrupt의 용도가 혼동될 수 있다. Interrupt를 두 번 연속으로 주면 될 
것 같지만 소용없다. 이런 때는 앞의 cancel 메소드를 사용하자.
notify vs notifyAll
Thread 상태 변화 다이어그램 
초기상태 
실행 
실행 가능 
실행 상태 
종료 
sleep 
TIME_WATING 
join 
TIME_WATING 
wait 
TIME_WATING 
BLOCKED 
Thread 생성 
Start 메소드 
schedule 
Run 메소드 종료 
GC 
sleep 
join 
wait 
Synchronized 
I/O 대기 등.. 
interrupt 
join 종료 
time out 
interrupt 
notify/notifyAll 
time out 
interrupt 
sleep 종료 
time out 
Lock 획득
notify는 wait set에서 대기하는 thread 중 하나만 실행하고 
notifyAll은 wait set에 들어가 있는 모든 thread를 실행한다. 
경쟁에서 밀린 thread는 다시 wait set으로 들어가게 된다.
그렇지만 notify가 더 좋다고 할 수는 없다. 
우선 notify가 깨우는 thread는 JVM과 운영체제가 결정하고 
어떤 thread가 동작하는지 알 수가 없다. 그리고…
public class LazyThread extends Thread{ 
public void run() { 
while (true) { 
try { 
synchronized (table) { 
table.wait(); 
} 
System.out.println(getName() + " is notified!"); 
} catch (InterruptedException e) { } 
} 
} 
} 
notify로 해당 thread를 깨우려고 해도 내부의 table.wait()만 깨우고 정작 
thread는 동작하지 않는다.
Exception
throws ?? try-catch?? 
public class FinallyThread extends Thread { 
public void run(){ 
try{ 
Thread.sleep(1000000); 
doWork(); 
}catch (InterruptedException e){ 
}finally { 
doShutdown(); 
} 
} 
private void doWork() throws InterruptedException { 
//동작 
Thread.sleep(500); 
} 
private void doShutdown(){ 
//종료작업 
} 
} 
자원을 Thread별로 잡고 있는 경우라면 반드시 
try-catch –finally로 close를 시켜줘야 한다.
Thread가 동작할 준비가 되었는가??
지나가기
import java.util.Queue; 
import java.util.LinkedList; 
public class RequestQueue { 
private final Queue<Request> queue = new LinkedList<Request>(); 
public synchronized Request getRequest() { 
if(queue.peek() == null) { 
return; 
} 
return queue.remove(); 
} 
public synchronized void putRequest(Request request) { 
queue.offer(request); 
} 
} 
return 에 boolean 값이나 null 값으로 처리형태를 알려줄 수 있다. 
혹은 throw exception으로 처리할 수도 있다.
기다리기
import java.util.Queue; 
import java.util.LinkedList; 
public class RequestQueue { 
private final Queue<Request> queue = new LinkedList<Request>(); 
public synchronized Request getRequest() { 
while(queue.peek() == null) { 
try { 
wait(); 
} catch (InterruptedException e) { 
} 
} 
return queue.remove(); 
} 
public synchronized void putRequest(Request request) { 
queue.offer(request); 
notifyAll(); 
} 
} 
while을 사용하는 이유는 wait상태에서 벗어난 후에도 조건을 확인해야하기 때문!!!
import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.LinkedBlockingQueue; 
public class RequestQueue { 
private final BlockingQueue<Request> queue = new LinkedBlockingQueue<Request>(); 
public Request getRequest() { 
Request req = null; 
try { 
req = queue.take(); 
} catch (InterruptedException e) { 
} 
return req; 
} 
public void putRequest(Request request) { 
try { 
queue.put(request); 
} catch (InterruptedException e) { 
} 
} 
}
BlockingQueue
ConcurrentLinkedQueue 
BlockingQueue가 아니지만 내부 데이터 구조를 세 
션으로 분할하여 멀티쓰레드 상황에서 수행능력을 
향상시킨다. 
BlockingQueue 
java.util.Queue interface의 sub-interface 
offer나 poll이 아닌 고유의 put, take를 통해 block 
기능을 제공한다. 
ArrayBlockingQueue 
요소의 상한이 있는 배열 베이스의 BlockingQueue 
LinkedBlockingQueue 
LinkedList에 대응하는 BlockingQueue 
DelayQueue 
Delayed 객체를 보관. 
지정 시간이 지나기 전에 take할 수 없고 오래된 요 
소부터 take한다.
기다리다 지나가기
import java.util.Queue; 
import java.util.LinkedList; 
public class RequestQueue { 
private final Queue<Request> queue = new LinkedList<Request>(); 
public synchronized Request getRequest() { 
long start = System.currentTimeMillis(); 
while(queue.peek() == null) { 
long now = System.currentTimeMillis(); 
long rest = 10000 - (now - start); 
if (rest <= 0) { 
return; 
} 
wait(rest); 
} 
return queue.remove(); 
} 
public synchronized void putRequest(Request request) { 
queue.offer(request); 
notifyAll(); 
} 
} 
10초 이상 대기하였을 때 notify가 들어오면 종료한다.
Producer-Consumer
데이터 중심의 역할 분배!!! 
• 응답성을 크게 향상시킬 수 있다!
producer queue consumer 
Data 저장 
Data 저장 
Data 저장 
Data 확인 
Data 확인 
Data 획득 
Data 확인 
Data 확인 
Data 확인 
Data 획득 
Data 처리 
처리 알림 
Data 처리 
처리 알림 
Data 생성 
Data 생성 
Data 생성
producer queue consumer 
Data 저장 
Data 저장 
Data 저장 
Data 확인 
Data 획득 
Data 확인 
Data 획득 
Data 처리 
처리 알림 
Data 처리 
처리 알림 
Data 생성 
Data 생성 
Data 생성 
Data 확인 
Data 획득 Data 처리 
처리 알림
public class Table { 
private final String[] buffer; 
private int tail; 
private int head; 
private int count; 
public Table(int count) { 
this.buffer = new String[count]; 
this.head = 0; 
this.tail = 0; 
this.count = 0; 
} 
public synchronized void put(String cake) 
throws InterruptedException { 
System.out.println( 
Thread.currentThread().getName() 
+ " puts " + cake); 
while (count >= buffer.length) { 
wait(); 
} 
buffer[tail] = cake; 
tail = (tail + 1) % buffer.length; 
count++; 
notifyAll(); 
} 
public synchronized String take() 
throws InterruptedException { 
while (count <= 0) { 
wait(); 
} 
String cake = buffer[head]; 
head = (head + 1) % buffer.length; 
count--; 
notifyAll(); 
System.out.println( 
Thread.currentThread().getName() 
+ " takes " + cake); 
return cake; 
} 
}
import java.util.Random; 
public class ProducerThread extends Thread { 
private final Random random; 
private final Table table; 
private static int id = 0; 
public ProducerThread(String name, 
Table table, long seed) { 
super(name); 
this.table = table; 
this.random = new Random(seed); 
} 
public void run() { 
try { 
while (true) { 
Thread.sleep(random.nextInt(1000)); 
String cake="[No."+nextId()+"]"; 
table.put(cake); 
} 
} catch (InterruptedException e) { 
} 
} 
private static synchronized int nextId() { 
return id++; 
} 
} 
import java.util.Random; 
public class ConsumerThread extends Thread { 
private final Random random; 
private final Table table; 
public ConsumerThread(String name, 
Table table, long seed) { 
super(name); 
this.table = table; 
this.random = new Random(seed); 
} 
public void run() { 
try { 
while (true) { 
String cake = table.take(); 
Thread.sleep(random.nextInt(1000)); 
} 
} catch (InterruptedException e) { 
} 
} 
}
상태 변화가 없는 읽기에는 Lock 걸 필요가 있을까??
쓰기 읽기 
쓰기 X X 
읽기 X O 
4가지 경우 중 하나만 Lock이 필요없지만….
쓰기와 읽기가 같은 빈도로 발생하지 않는다! 
만약 쓰기 thread보다 읽기 thread의 처리가 무겁거나 
쓰기 threa보다 읽기 threa의 빈도가 더 높은 경우라면 
읽기끼리 lock 해제하여 수행 능력을 높일 수 있다!!!! 
Java에서는 ReentrantReadWriteLock class에서 ReadLock, WriteLock을 제공
public class Table { 
private final String[] buffer; 
private int tail; 
private int head; 
private int count; 
private final ReadWriteLock lock 
= new ReentrantReadWriteLock(true /* fair */); 
private final Lock readLock = lock.readLock(); 
private final Lock writeLock = lock.writeLock(); 
public Table(int count) { 
this.buffer = new String[count]; 
this.head = 0; 
this.tail = 0; 
this.count = 0; 
} 
public void put(String cake) 
throws InterruptedException { 
writeLock.lock(); 
try { 
slowly(); 
buffer[tail] = cake; 
tail = (tail + 1) % buffer.length; 
count++; 
} finally { 
writeLock.unlock(); 
} 
} 
public String take() throws InterruptedException { 
writeLock.lock(); 
try { 
return doTake(); 
} finally { 
writeLock.unlock(); 
} 
} 
private String doTake() { 
String cake = buffer[head]; 
head = (head + 1) % buffer.length; 
count--; 
return cake; 
} 
private void slowly() { 
try { 
Thread.sleep(50); 
} catch (InterruptedException e) { } 
} 
public int getSize(){ 
readLock.lock(); 
try { 
return count; 
} finally { 
readLock.unlock(); 
} 
} 
}
Consumer의 위임
producer table client 
Data 저장 
Data 확인 
Data 획득 
Data 획득 
Data 처리 
처리 알림 
Data 처리 
처리 알림 
Data 생성 
consumer 
Data 저장 
Data 생성 
Data 저장 
Data 생성 
Data 확인 
작업 의뢰 
접수 알림 
작업 의뢰 
접수 알림
import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 
public class Main { 
public static final BlockingQueue<String> table 
= new LinkedBlockingQueue<String>(); 
public static void main(String[] args) { 
public static void main(String[] args) { 
new ProducerThread("mThread-1", 31415).start(); 
new ClientThread("Client-Thread").start(); 
} 
} 
public class ClientThread extends Thread { 
public ClientThread(String name) { 
super(name); 
} 
public void run() { 
try { 
while(true){ 
if(Main.table.getSize()>0) { 
new ConsumerThread().start(); 
}else{ Thread.sleep(1000); } 
} 
}catch(RejectedExecutionException e) { 
System.out.println( 
Thread.currentThread().getName() 
+ ":" + e); 
}catch(CancellationException e) { 
System.out.println( 
Thread.currentThread().getName() 
+ ":" + e); 
}catch(InterruptedException e) { 
System.out.println( 
Thread.currentThread().getName() 
+ ":" + e); 
} 
} 
} 
Consumer Thread가 직접 일을 받아 처리하지 않고 
해당 작업을 받아서 Consumer thread에게 분배하는 
Thread를 생성하자.
public class ClientThread extends Thread { 
ExecutorService executor; 
public ClientThread(String name, 
ExecutorService executor) { 
super(name); 
this.executor = executor; 
} 
public void run() { 
try { 
while(true){ 
if(Main.table.getSize()>0) { 
executor.execute(new ConsumerThread()); 
}else{ Thread.sleep(1000); } 
} 
} catch (RejectedExecutionException e) { 
System.out.println( 
Thread.currentThread().getName()+":"+e); 
} catch (CancellationException e) { 
System.out.println( 
Thread.currentThread().getName()+":"+e); 
} catch (InterruptedException e) { 
System.out.println( 
Thread.currentThread().getName()+":"+e); 
} 
} 
} 
import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 
public class Main { 
public static void main(String[] args) { 
ExecutorService executor 
= Executors.newCachedThreadPool(); 
public static void main(String[] args) { 
new ProducerThread("MakerThread-1",31415).start(); 
new ClientThread("Cleint-Thread",executor).start(); 
} 
}
응답성을 높여 지연시간을 줄일 수 있다. 
작업을 실행함과 동시에 작업 실행 여부를 Producer에게 알려줄 수 있다. 
VS 
작업 결과에 Return 값을 받을 수 없다. 
요청이 들어올 때마다 Thread가 생기기 때문에 메모리 부족이 발생할 수 있다. 
Thread로 실행하기 때문에 순차적으로 작업이 처리되지 않는다.
Consumer의 대기
public class ClientThread extends Thread { 
private final ExecutorService executorService; 
public ClientThread( 
ExecutorService executorService) { 
this.executorService = executorService; 
} 
public void run() { 
try { 
while( 
!Thread.currentThread().isInterrupted()) { 
while(Main.table.getSize()>0){ 
ConsumerThread request = 
new ConsumerThread(getName()); 
executorService.execute(request); 
} 
} 
} catch (RejectedExecutionException e) { 
System.out.println(getName() + " : " + e); 
} 
} 
} 
public class ConsumerThread extends Thread { 
private final String parentName; 
public ConsumerThread(String name) { 
parentName = name; 
} 
public void run() { 
try { 
String cake = Main.table.take(); 
if(cake != null) 
System.out.println( 
parentName + getName() 
+ " eat " + cake); 
Thread.sleep(100); 
} catch (InterruptedException e) { 
} 
} 
}
import java.util.concurrent.BlockingQueue; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.LinkedBlockingQueue; 
public class Main { 
public static final BlockingQueue<String> table = new LinkedBlockingQueue<String>(); 
public static void main(String[] args) { 
new ProducerThread("MakerThread-1", 31415).start(); 
ExecutorService executorService = Executors.newFixedThreadPool(5); 
try { 
new ClientThread(executorService).start(); 
} finally { 
//executorService.shutdown(); 
} 
} 
} 
Thread-0Thread-1 eat [No.0] 
Thread-0Thread-2 eat [No.1] 
Thread-0Thread-3 eat [No.2] 
Thread-0Thread-4 eat [No.3] 
Thread-0Thread-5 eat [No.4] 
……
ExecutorService executorService=Executors.newFixedThreadPool(5); 
처음부터 Thread 5개를 실행되어 더 이상 추가되거나 줄지 않는다. 
• Thread를 돌려가면서 사용하면 Thread를 새로 만들고 실행하는데 걸리는 
시간을 줄여 성능을 향상시킬 수 있다. 
• 대신 Worker Thread의 처리능력을 초과하는 Request가 들어오는 경우 
Channel에 Request가 늘어나게 되고 메모리 용량을 차지하게 된다.
Future
응답성을 높여 지연시간을 줄일 수 있다. 
작업을 실행함과 동시에 작업 실행 여부를 Producer에게 알려줄 수 있다. 
VS 
작업 결과에 Return 값을 받을 수 없다. 
요청이 들어올 때마다 Thread가 생기기 때문에 메모리 부족이 발생할 수 있다. 
Thread로 실행하기 때문에 순차적으로 작업이 처리되지 않는다.
응답성을 높여 지연시간을 줄일 수 있다. 
작업을 실행함과 동시에 작업 실행 여부를 Producer에게 알려줄 수 있다. 
CPU가 할당받지 않는 유후 시간이 존재하는 상황이라면!!! 
응답성 뿐 아니라 처리량 또한 비약적으로 향상된다!!!
return 값을 준비하는 부분과 
return 값을 이용하는 부분을 
분리해서 구현해보자!! 
작업 결과에 Return 값을 받을 수 없다????
public class ConsumerThread 
extends Thread { 
public void run() { 
try { 
String cake = Main.table.take(); 
if(cake != null) 
System.out.println(getName() 
+ " eat " + cake); 
Thread.sleep(100); 
} catch (InterruptedException e) { 
} 
} 
} 
public class ConsumerClientThread extends Thread { 
ExecutorService service; 
public ConsumerClientThread(String name, 
ExecutorService executorService) { 
super(name); 
service = executorService; 
} 
public void run() { 
try { 
while(true){ 
/*while(Main.table.getSize()>0){*/ 
service.execute(new ConsumerThread()); 
Thread.sleep(200); 
/*}*/ 
} 
} catch (RejectedExecutionException e) { 
System.out.println(Thread.currentThread().getName() + ":" + e); 
} catch (CancellationException e) { 
System.out.println(Thread.currentThread().getName() + ":" + e); 
} catch (InterruptedException e) { 
System.out.println(Thread.currentThread().getName() + ":" + e); 
} 
} 
}
public class ProducerThread 
implements Callable<String> { 
private final Random random; 
private static int id = 0; 
public ProducerThread(long seed) { 
this.random = new Random(seed); 
} 
public String call() { 
String cake="[No." + nextId() + "]"; 
try { 
Thread.sleep(random.nextInt(1000)); 
Main.table.put(cake); 
} catch (InterruptedException e) { 
} 
return cake; 
} 
private static synchronized int nextId( 
) { 
return id++; 
} 
} 
public class ProducerClientThread extends Thread { 
ExecutorService service; 
public ProducerClientThread(String name, 
ExecutorService executorService) { 
super(name); 
service = executorService; 
} 
public void run() { 
try { 
while(true){ 
Future<String> future 
= service.submit(new ProducerThread(31415)); 
Thread.sleep(10); 
String value = future.get(); 
System.out.println(Thread.currentThread().getName() 
+ ": value = " + value); 
} 
} catch (RejectedExecutionException e) { 
System.out.println(Thread.currentThread().getName() + ":" + e); 
} catch (CancellationException e) { 
System.out.println(Thread.currentThread().getName() + ":" + e); 
} catch (ExecutionException e) { 
System.out.println(Thread.currentThread().getName() + ":" + e); 
} catch (InterruptedException e) { 
System.out.println(Thread.currentThread().getName() + ":" + e); 
} 
} 
}
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
public class Main { 
public static final BlockingQueue<String> table 
= new LinkedBlockingQueue<String>(); 
public static void main(String[] args) { 
ExecutorService mExecutorService 
= Executors.newSingleThreadExecutor(); 
ExecutorService eExecutorService 
= Executors.newFixedThreadPool(5); 
try { 
new ProducerClientThread("mThread-1", 
mExecutorService).start(); 
new ProducerClientThread("mThread-2", 
mExecutorService).start(); 
new ConsumerClientThread("eThread", 
eExecutorService).start(); 
Thread.sleep(2000); 
} catch (InterruptedException e) { 
} finally { 
System.out.println("*** shutdown ***"); 
makeExecutorService.shutdown(); 
eatExecutorService.shutdown(); 
} 
} 
} 
mThread-2: value = [No.0] 
Thread-0 eat [No.0] 
mThread-1: value = [No.1] 
Thread-1 eat [No.1] 
mThread-2: value = [No.2] 
Thread-2 eat [No.2] 
mThread-1: value = [No.3] 
Thread-3 eat [No.3] 
mThread-2: value = [No.4] 
Thread-4 eat [No.4] 
mThread-1: value = [No.5] 
Thread-5 eat [No.5] 
mThread-2: value = [No.6] 
Thread-6 eat [No.6] 
mThread-1: value = [No.7] 
Thread-7 eat [No.7] 
*** shutdown *** 
eThread:java.util.concurrent.RejectedExecutionException: Task 
Thread[Thread-10,5,main] rejected from 
java.util.concurrent.ThreadPoolExecutor@7ec00b12[Shutting down, 
pool size = 3, active threads = 3, queued tasks = 0, completed tasks 
= 7] 
mThread-2: value = [No.8] 
Thread-8 eat [No.8] 
mThread-2:java.util.concurrent.RejectedExecutionException: Task 
java.util.concurrent.FutureTask@2e85d205 rejected from 
java.util.concurrent.ThreadPoolExecutor@2bc62c46[Shutting down, 
pool size = 1, active threads = 1, queued tasks = 0, completed tasks 
= 9] 
mThread-1: value = [No.9] 
Thread-9 eat [No.9] 
mThread-1:java.util.concurrent.RejectedExecutionException: Task 
java.util.concurrent.FutureTask@1dfec55f rejected from 
java.util.concurrent.ThreadPoolExecutor@2bc62c46[Terminated, pool 
size = 0, active threads = 0, queued tasks = 0, completed tasks = 10]
감사합니다.

Mais conteúdo relacionado

Mais procurados

[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요NAVER D2
 
Concurrency in action - chapter 5
Concurrency in action - chapter 5Concurrency in action - chapter 5
Concurrency in action - chapter 5JinWoo Lee
 
비동기 파일 로딩
비동기 파일 로딩비동기 파일 로딩
비동기 파일 로딩Bongseok Cho
 
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현NAVER D2
 
242 naver-2
242 naver-2242 naver-2
242 naver-2NAVER D2
 
LockFree Algorithm
LockFree AlgorithmLockFree Algorithm
LockFree AlgorithmMerry Merry
 
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요? 내훈 정
 
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화sung ki choi
 
11_웹서비스활용
11_웹서비스활용11_웹서비스활용
11_웹서비스활용noerror
 
[NDC 2016] 유니티, iOS에서 LINQ 사용하기
[NDC 2016] 유니티, iOS에서 LINQ 사용하기[NDC 2016] 유니티, iOS에서 LINQ 사용하기
[NDC 2016] 유니티, iOS에서 LINQ 사용하기Daehee Kim
 
Design Pattern - Multithread Ch10
Design Pattern - Multithread Ch10Design Pattern - Multithread Ch10
Design Pattern - Multithread Ch10hyun soomyung
 
[160402_데브루키_박민근] UniRx 소개
[160402_데브루키_박민근] UniRx 소개[160402_데브루키_박민근] UniRx 소개
[160402_데브루키_박민근] UniRx 소개MinGeun Park
 
Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용흥배 최
 
android_thread
android_threadandroid_thread
android_threadhandfoot
 
07 스레드스케줄링,우선순위,그리고선호도
07 스레드스케줄링,우선순위,그리고선호도07 스레드스케줄링,우선순위,그리고선호도
07 스레드스케줄링,우선순위,그리고선호도ssuser3fb17c
 
Free rtos seminar
Free rtos seminarFree rtos seminar
Free rtos seminarCho Daniel
 
windows via c++ Ch 5. Job
windows via c++ Ch 5. Jobwindows via c++ Ch 5. Job
windows via c++ Ch 5. JobHyosung Jeon
 
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)내훈 정
 
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용 [Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용 MinGeun Park
 

Mais procurados (20)

[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
 
Concurrency in action - chapter 5
Concurrency in action - chapter 5Concurrency in action - chapter 5
Concurrency in action - chapter 5
 
비동기 파일 로딩
비동기 파일 로딩비동기 파일 로딩
비동기 파일 로딩
 
Ndc12 2
Ndc12 2Ndc12 2
Ndc12 2
 
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
[2D7]레기온즈로 살펴보는 확장 가능한 게임서버의 구현
 
242 naver-2
242 naver-2242 naver-2
242 naver-2
 
LockFree Algorithm
LockFree AlgorithmLockFree Algorithm
LockFree Algorithm
 
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요? (2013 DEVIEW) 멀티쓰레드 프로그래밍이  왜이리 힘드나요?
(2013 DEVIEW) 멀티쓰레드 프로그래밍이 왜이리 힘드나요?
 
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화
제프리 리처의 Windows via C/C++ : 8장 유저 모드에서의 스레드 동기화
 
11_웹서비스활용
11_웹서비스활용11_웹서비스활용
11_웹서비스활용
 
[NDC 2016] 유니티, iOS에서 LINQ 사용하기
[NDC 2016] 유니티, iOS에서 LINQ 사용하기[NDC 2016] 유니티, iOS에서 LINQ 사용하기
[NDC 2016] 유니티, iOS에서 LINQ 사용하기
 
Design Pattern - Multithread Ch10
Design Pattern - Multithread Ch10Design Pattern - Multithread Ch10
Design Pattern - Multithread Ch10
 
[160402_데브루키_박민근] UniRx 소개
[160402_데브루키_박민근] UniRx 소개[160402_데브루키_박민근] UniRx 소개
[160402_데브루키_박민근] UniRx 소개
 
Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용Twitter의 snowflake 소개 및 활용
Twitter의 snowflake 소개 및 활용
 
android_thread
android_threadandroid_thread
android_thread
 
07 스레드스케줄링,우선순위,그리고선호도
07 스레드스케줄링,우선순위,그리고선호도07 스레드스케줄링,우선순위,그리고선호도
07 스레드스케줄링,우선순위,그리고선호도
 
Free rtos seminar
Free rtos seminarFree rtos seminar
Free rtos seminar
 
windows via c++ Ch 5. Job
windows via c++ Ch 5. Jobwindows via c++ Ch 5. Job
windows via c++ Ch 5. Job
 
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이  왜 이리 힘드나요?  (Lock-free에서 Transactional Memory까지)
Ndc2014 시즌 2 : 멀티쓰레드 프로그래밍이 왜 이리 힘드나요? (Lock-free에서 Transactional Memory까지)
 
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용 [Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
[Unite17] 유니티에서차세대프로그래밍을 UniRx 소개 및 활용
 

Semelhante a Multi-thread : producer - consumer

Multithread design pattern
Multithread design patternMultithread design pattern
Multithread design pattern종빈 오
 
[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성NAVER D2
 
Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기jongho jeong
 
Introduction to Fork Join Framework_SYS4U I&C
Introduction to Fork Join Framework_SYS4U I&CIntroduction to Fork Join Framework_SYS4U I&C
Introduction to Fork Join Framework_SYS4U I&Csys4u
 
Clean code
Clean codeClean code
Clean codebbongcsu
 
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블YongEun Choi
 
프론트엔드스터디 E05 js closure oop
프론트엔드스터디 E05 js closure oop프론트엔드스터디 E05 js closure oop
프론트엔드스터디 E05 js closure oopYoung-Beom Rhee
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)문익 장
 
Angular2 가기전 Type script소개
 Angular2 가기전 Type script소개 Angular2 가기전 Type script소개
Angular2 가기전 Type script소개Dong Jun Kwon
 
[오픈소스컨설팅]Fault Tolerance Architecture by Netflix
[오픈소스컨설팅]Fault Tolerance Architecture by Netflix[오픈소스컨설팅]Fault Tolerance Architecture by Netflix
[오픈소스컨설팅]Fault Tolerance Architecture by NetflixJi-Woong Choi
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509영석 조
 
자바스크립트 함수
자바스크립트 함수자바스크립트 함수
자바스크립트 함수유진 변
 
TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기Wonchang Song
 
일단 시작하는 코틀린
일단 시작하는 코틀린일단 시작하는 코틀린
일단 시작하는 코틀린Park JoongSoo
 
Java mentoring of samsung scsc 2
Java mentoring of samsung scsc   2Java mentoring of samsung scsc   2
Java mentoring of samsung scsc 2도현 김
 

Semelhante a Multi-thread : producer - consumer (20)

Multithread design pattern
Multithread design patternMultithread design pattern
Multithread design pattern
 
[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성[2D4]Python에서의 동시성_병렬성
[2D4]Python에서의 동시성_병렬성
 
Gcd ppt
Gcd pptGcd ppt
Gcd ppt
 
Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기Javascript 조금 더 잘 알기
Javascript 조금 더 잘 알기
 
Spring Boot 2
Spring Boot 2Spring Boot 2
Spring Boot 2
 
Introduction to Fork Join Framework_SYS4U I&C
Introduction to Fork Join Framework_SYS4U I&CIntroduction to Fork Join Framework_SYS4U I&C
Introduction to Fork Join Framework_SYS4U I&C
 
Clean code
Clean codeClean code
Clean code
 
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
(국비지원/실업자교육/재직자교육/스프링교육/마이바티스교육추천)#13.스프링프레임워크 & 마이바티스 (Spring Framework, MyB...
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블
 
프론트엔드스터디 E05 js closure oop
프론트엔드스터디 E05 js closure oop프론트엔드스터디 E05 js closure oop
프론트엔드스터디 E05 js closure oop
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
 
Java(2/4)
Java(2/4)Java(2/4)
Java(2/4)
 
Angular2 가기전 Type script소개
 Angular2 가기전 Type script소개 Angular2 가기전 Type script소개
Angular2 가기전 Type script소개
 
[오픈소스컨설팅]Fault Tolerance Architecture by Netflix
[오픈소스컨설팅]Fault Tolerance Architecture by Netflix[오픈소스컨설팅]Fault Tolerance Architecture by Netflix
[오픈소스컨설팅]Fault Tolerance Architecture by Netflix
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509
 
자바스크립트 함수
자바스크립트 함수자바스크립트 함수
자바스크립트 함수
 
Java(3/4)
Java(3/4)Java(3/4)
Java(3/4)
 
TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기TDD.JUnit.조금더.알기
TDD.JUnit.조금더.알기
 
일단 시작하는 코틀린
일단 시작하는 코틀린일단 시작하는 코틀린
일단 시작하는 코틀린
 
Java mentoring of samsung scsc 2
Java mentoring of samsung scsc   2Java mentoring of samsung scsc   2
Java mentoring of samsung scsc 2
 

Último

MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionMOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionKim Daeun
 
캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스
 
Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Wonjun Hwang
 
Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Wonjun Hwang
 
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Kim Daeun
 
A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)Tae Young Lee
 

Último (6)

MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionMOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
 
캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차
 
Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)
 
Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)
 
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
 
A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)
 

Multi-thread : producer - consumer

  • 3. 한발 더 나아가 미래를 내다보는 개발자는 이미 단일 컴퓨터 내부에서의 병렬 처리를 넘어 여러 대의 컴퓨터에서 병렬로 동작하는 플랫폼을 찾고 있고, 이와 같은 분산 병렬 처리 플랫 폼이 한창 인기를 얻는 요즘입니다. 이런 대규모 병렬 처리 플 랫홈도 중요하긴 하지만, 그 안에서는 항상 단일 프로세스 내 부에서 동작하는 여러 스레드가 안정적으로 실행되도록 하는 병렬 처리 기법이 적용되어 있음을 잊어서는 안될 것입니다. -’멀티 코어를 100% 활용하는 자바 병렬 프로그래밍’ 옮김이의 말 중
  • 5. 1. Safety 2. Liveness 3. Performance 4. Reuse
  • 7. Thread에서 사용되는 값들의 무결성 정확성!!  Class가 해당 Class 명세에 부합한다. 해당 객체가 어떻게 생성되고 어떻게 사용되든 다른 작업 없이, 정확하게 동작하면 해당 객체는 Thread Safe 하다고 말한다.
  • 9. 원하는 일이 원하는 시기에 동작한다. Deadlock  서로 다른 Thread가 서로 가지고 있는 독점 자원을 원하여 멈춰져 있는 현상 Starvation  Thread가 자신이 원하는 자원을 할당받지 못하는 상황 Livelock  서로 다른 Thread가 서로의 상태를 변경하면서 작업을 진행하지 못하는 현상
  • 11. Muti-Thread 환경은 Single-Thread 환경보다 성능상 자원을 더 소모한다. (락, 메모리, 상태 설정, Thread 생성 및 제거 등등) Muti-thread는 결국!! 이런 비용을 지불하여 응답성과 처리용량을 늘리는 것이 목적!!! 서로 다른 Thread의 경쟁할 일이 없는 synchonized 블록의 경우 JVM 수준에서 동 기화 관련 부하를 줄이거나 아예 없애주기 때문에 매우 빠르게 동작한다. 실행 순 서를 고정하는 역할을 할 수도 있다. 대신 경쟁조건이 존재하는 경우 대부분의 JVM이 운영체제의 기능을 호출한다.
  • 13. Synchonized 키워드, volatile 키워드 • 성능을 위해서는 위에 두 키워드 뿐 아니라 Multi-thread의 기법에 들 어가는 모든 기술을 최소화하는 것이 좋다. 하지만!! 이런 키워드가 없는 경우 • 후임 개발자가 해당 Class에 대해 Thread safe 여부를 판단하기 힘들고 더 심한 경우에는 이미 안전한 Class를 망칠 수도 있다.
  • 14. Synchonized 키워드를 사용하지 않아도 되는 Class
  • 15. public final class Person { private final String name; public Person(String name){ this.name = name; } public String getName(){ return name; } public String toString(){ return "Person :: name = "+name; } } public class SyncPerson { private String name; public SyncPerson(String name){ this.name = name; } public synchronized String getName(){ return name; } public synchronized void setName(String name){ this.name = name; } public synchronized String toString(){ return "Person :: name = " + name; } } for(long i=0;i<CALL_COUNT;i++){ Person person = new Person(String.valueOf(i)); person.toString(); } SyncPerson syncPerson = new SyncPerson(String.valueOf(0)); for(long i=0;i<CALL_COUNT;i++){ syncPerson.setName(String.valueOf(i)); syncPerson.toString(); } Person : BEGIN Person : END time = 475msec. SyncPerson : BEGIN SyncPerson : END time = 523msec.
  • 16. public class TestMain { private static final long CALL_COUNT = 10000000L; public static void main(String args []){ new PersonConsumerThread("Single Thread",CALL_COUNT).run(); Thread t1 = new PersonConsumerThread("Thread-0", CALL_COUNT/2); t1.start(); Thread t2 = new PersonConsumerThread("Thread-1", CALL_COUNT/2); t2.start(); //대기 try { t1.join(); t2.join(); } catch(InterruptedException e) { } SyncPerson syncPerson = new SyncPerson(String.valueOf(0)); new SyncPersonCounmerThread("Single Thread", CALL_COUNT, syncPerson).run(); Thread t4 = new SyncPersonCounmerThread("Thread-4", CALL_COUNT/2, syncPerson); t4.start(); Thread t5 = new SyncPersonCounmerThread("Thread-5", CALL_COUNT/2, syncPerson); t5.start(); } } Single Thread Person : BEGIN 10000000 Single Thread Person : END time = 534msec. Main Person : BEGIN Thread-0 Person : BEGIN 5000000 Thread-1 Person : BEGIN 5000000 Thread-0 Person : END time = 297msec. Thread-1 Person : END time = 298msec. Main Person : END time = 298msec. Single Thread SyncPerson : BEGIN 10000000 Single Thread SyncPerson : END time = 668msec. Main SyncPerson : BEGIN Thread-4 SyncPerson : BEGIN 5000000 Thread-5 SyncPerson : BEGIN 5000000 Thread-4 SyncPerson : END time = 1110msec. Thread-5 SyncPerson : END time = 1121msec. Main SyncPerson : END time = 1790msec.
  • 17. Immutable??? public static void main(String args[]){ Person person = new Person(“bob”); …… System.out.println(person.getName()); System.out.println(person.getName().replace(“b”,”p”)); System.out.println(person.getName()); } bob pop bob String의 replace()를 사용하면 실제 객체 값에는 영향을 주지 않고 그 순간만 데이터를 바꿔서 사용할 수 있다.
  • 18. String에 replace() 같은 메소드가 문제! 다행히 String에서는 별 문제 없지만…
  • 19. public final class Person { private final StringBuffer info; public Person(String name){ this.info = new StringBuffer("name = “ + name); } public StringBuffer getInfo(){ return info; } public String toString(){ return "Person :: " + info; } } public static void main(String args[]){ Person person = new Person("bob"); System.out.println(person); StringBuffer name = person.getInfo(); name.replace(7,10,"pop"); System.out.println(person); } Person :: name = bob Person :: name = pop StringBuffer에서 제공하는 replace()는 String과 달리 객체에 직접 영향을 주기 때문에 Muti-thread 환경에서 안전하지 못하다.
  • 21. Thread의 종료는 매우 중요하다!! • Thread가 정상적으로 종료하지 않으면, -> JVM 역시 종료되지 않고 대기하게 된다.
  • 22. 실행 중인 Thread를 그냥 종료??? • 작업 중이던 메모리에 대한 반환 작업이 정상적으로 이뤄지지 않는 경우도 있고 해서 Thread.stop()과 Thread.suspend()와 같은 메소드는 사용하면 안된다!!!(deprecated) JAVA에서는 특정 Thread를 종료시키면 안된다!! • 작업을 수행하는 Thread와 해당 Thread의 종료를 요청하는 Thread를 같이 생각해야 한다.
  • 23. public class PrimeGenerator implements Runnable{ private final List<BigInteger> primes = new ArrayList<BigInteger>(); private volatile Boolean cancelled; public void run(){ BigInteger p = BigInteger.ONE; while(!cancelled) { p = p.nextProbablePrime(); synchronized(this){ primes.add(p); } } } public void cancel(){ cancelled = true; } } 해당 cancel 메소드를 호출하면 해당 Thread는 작업을 정리한 후에 종료한다.
  • 24. 해당 Thread가 sleep이나 join같은 대기 상태라면???
  • 25. public class PrimeGenerator implements Runnable{ private final List<BigInteger> primes = new ArrayList<BigInteger>(); private volatile Boolean cancelled; public void run(){ BigInteger p = BigInteger.ONE; Thread.sleep(1000000); while(!cancelled) { p = p.nextProbablePrime(); synchronized(this){ primes.add(p); } } } public void cancel(){ cancelled = true; } } 해당 Thread는 대기기간이 끝날 때까지 Cancelled가 아무런 영향도 미치지 못한다.
  • 26. Interrupted 를 활용하자!! public class PrimeGenerator implements Runnable{ private final List<BigInteger> primes = new ArrayList<BigInteger>(); public void run(){ BigInteger p = BigInteger.ONE; while(!Thread.currentThread().isInterrupted()) { p = p.nextProbablePrime(); synchronized(this){ primes.add(p); } } } }
  • 27. public class PrimeGenerator implements Runnable{ private final List<BigInteger> primes = new ArrayList<BigInteger>(); public void run(){ BigInteger p = BigInteger.ONE; while(!Thread.currentThread().isInterrupted()) { try { Thread.sleep(5000); } catch (InterruptedException e) { } p = p.nextProbablePrime(); synchronized(this){ primes.add(p); } } } } 다른 대기 중이라도 interrupt가 발생하면 대기 상태에서 벗어나게 된다. 종종 대기 상태인 thread를 깨우기 위해 interrupt를 사용하는 경우 두 interrupt의 용도가 혼동될 수 있다. Interrupt를 두 번 연속으로 주면 될 것 같지만 소용없다. 이런 때는 앞의 cancel 메소드를 사용하자.
  • 29. Thread 상태 변화 다이어그램 초기상태 실행 실행 가능 실행 상태 종료 sleep TIME_WATING join TIME_WATING wait TIME_WATING BLOCKED Thread 생성 Start 메소드 schedule Run 메소드 종료 GC sleep join wait Synchronized I/O 대기 등.. interrupt join 종료 time out interrupt notify/notifyAll time out interrupt sleep 종료 time out Lock 획득
  • 30. notify는 wait set에서 대기하는 thread 중 하나만 실행하고 notifyAll은 wait set에 들어가 있는 모든 thread를 실행한다. 경쟁에서 밀린 thread는 다시 wait set으로 들어가게 된다.
  • 31. 그렇지만 notify가 더 좋다고 할 수는 없다. 우선 notify가 깨우는 thread는 JVM과 운영체제가 결정하고 어떤 thread가 동작하는지 알 수가 없다. 그리고…
  • 32. public class LazyThread extends Thread{ public void run() { while (true) { try { synchronized (table) { table.wait(); } System.out.println(getName() + " is notified!"); } catch (InterruptedException e) { } } } } notify로 해당 thread를 깨우려고 해도 내부의 table.wait()만 깨우고 정작 thread는 동작하지 않는다.
  • 34. throws ?? try-catch?? public class FinallyThread extends Thread { public void run(){ try{ Thread.sleep(1000000); doWork(); }catch (InterruptedException e){ }finally { doShutdown(); } } private void doWork() throws InterruptedException { //동작 Thread.sleep(500); } private void doShutdown(){ //종료작업 } } 자원을 Thread별로 잡고 있는 경우라면 반드시 try-catch –finally로 close를 시켜줘야 한다.
  • 37. import java.util.Queue; import java.util.LinkedList; public class RequestQueue { private final Queue<Request> queue = new LinkedList<Request>(); public synchronized Request getRequest() { if(queue.peek() == null) { return; } return queue.remove(); } public synchronized void putRequest(Request request) { queue.offer(request); } } return 에 boolean 값이나 null 값으로 처리형태를 알려줄 수 있다. 혹은 throw exception으로 처리할 수도 있다.
  • 39. import java.util.Queue; import java.util.LinkedList; public class RequestQueue { private final Queue<Request> queue = new LinkedList<Request>(); public synchronized Request getRequest() { while(queue.peek() == null) { try { wait(); } catch (InterruptedException e) { } } return queue.remove(); } public synchronized void putRequest(Request request) { queue.offer(request); notifyAll(); } } while을 사용하는 이유는 wait상태에서 벗어난 후에도 조건을 확인해야하기 때문!!!
  • 40. import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class RequestQueue { private final BlockingQueue<Request> queue = new LinkedBlockingQueue<Request>(); public Request getRequest() { Request req = null; try { req = queue.take(); } catch (InterruptedException e) { } return req; } public void putRequest(Request request) { try { queue.put(request); } catch (InterruptedException e) { } } }
  • 42. ConcurrentLinkedQueue BlockingQueue가 아니지만 내부 데이터 구조를 세 션으로 분할하여 멀티쓰레드 상황에서 수행능력을 향상시킨다. BlockingQueue java.util.Queue interface의 sub-interface offer나 poll이 아닌 고유의 put, take를 통해 block 기능을 제공한다. ArrayBlockingQueue 요소의 상한이 있는 배열 베이스의 BlockingQueue LinkedBlockingQueue LinkedList에 대응하는 BlockingQueue DelayQueue Delayed 객체를 보관. 지정 시간이 지나기 전에 take할 수 없고 오래된 요 소부터 take한다.
  • 44. import java.util.Queue; import java.util.LinkedList; public class RequestQueue { private final Queue<Request> queue = new LinkedList<Request>(); public synchronized Request getRequest() { long start = System.currentTimeMillis(); while(queue.peek() == null) { long now = System.currentTimeMillis(); long rest = 10000 - (now - start); if (rest <= 0) { return; } wait(rest); } return queue.remove(); } public synchronized void putRequest(Request request) { queue.offer(request); notifyAll(); } } 10초 이상 대기하였을 때 notify가 들어오면 종료한다.
  • 46. 데이터 중심의 역할 분배!!! • 응답성을 크게 향상시킬 수 있다!
  • 47. producer queue consumer Data 저장 Data 저장 Data 저장 Data 확인 Data 확인 Data 획득 Data 확인 Data 확인 Data 확인 Data 획득 Data 처리 처리 알림 Data 처리 처리 알림 Data 생성 Data 생성 Data 생성
  • 48. producer queue consumer Data 저장 Data 저장 Data 저장 Data 확인 Data 획득 Data 확인 Data 획득 Data 처리 처리 알림 Data 처리 처리 알림 Data 생성 Data 생성 Data 생성 Data 확인 Data 획득 Data 처리 처리 알림
  • 49. public class Table { private final String[] buffer; private int tail; private int head; private int count; public Table(int count) { this.buffer = new String[count]; this.head = 0; this.tail = 0; this.count = 0; } public synchronized void put(String cake) throws InterruptedException { System.out.println( Thread.currentThread().getName() + " puts " + cake); while (count >= buffer.length) { wait(); } buffer[tail] = cake; tail = (tail + 1) % buffer.length; count++; notifyAll(); } public synchronized String take() throws InterruptedException { while (count <= 0) { wait(); } String cake = buffer[head]; head = (head + 1) % buffer.length; count--; notifyAll(); System.out.println( Thread.currentThread().getName() + " takes " + cake); return cake; } }
  • 50. import java.util.Random; public class ProducerThread extends Thread { private final Random random; private final Table table; private static int id = 0; public ProducerThread(String name, Table table, long seed) { super(name); this.table = table; this.random = new Random(seed); } public void run() { try { while (true) { Thread.sleep(random.nextInt(1000)); String cake="[No."+nextId()+"]"; table.put(cake); } } catch (InterruptedException e) { } } private static synchronized int nextId() { return id++; } } import java.util.Random; public class ConsumerThread extends Thread { private final Random random; private final Table table; public ConsumerThread(String name, Table table, long seed) { super(name); this.table = table; this.random = new Random(seed); } public void run() { try { while (true) { String cake = table.take(); Thread.sleep(random.nextInt(1000)); } } catch (InterruptedException e) { } } }
  • 51. 상태 변화가 없는 읽기에는 Lock 걸 필요가 있을까??
  • 52. 쓰기 읽기 쓰기 X X 읽기 X O 4가지 경우 중 하나만 Lock이 필요없지만….
  • 53. 쓰기와 읽기가 같은 빈도로 발생하지 않는다! 만약 쓰기 thread보다 읽기 thread의 처리가 무겁거나 쓰기 threa보다 읽기 threa의 빈도가 더 높은 경우라면 읽기끼리 lock 해제하여 수행 능력을 높일 수 있다!!!! Java에서는 ReentrantReadWriteLock class에서 ReadLock, WriteLock을 제공
  • 54. public class Table { private final String[] buffer; private int tail; private int head; private int count; private final ReadWriteLock lock = new ReentrantReadWriteLock(true /* fair */); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public Table(int count) { this.buffer = new String[count]; this.head = 0; this.tail = 0; this.count = 0; } public void put(String cake) throws InterruptedException { writeLock.lock(); try { slowly(); buffer[tail] = cake; tail = (tail + 1) % buffer.length; count++; } finally { writeLock.unlock(); } } public String take() throws InterruptedException { writeLock.lock(); try { return doTake(); } finally { writeLock.unlock(); } } private String doTake() { String cake = buffer[head]; head = (head + 1) % buffer.length; count--; return cake; } private void slowly() { try { Thread.sleep(50); } catch (InterruptedException e) { } } public int getSize(){ readLock.lock(); try { return count; } finally { readLock.unlock(); } } }
  • 56. producer table client Data 저장 Data 확인 Data 획득 Data 획득 Data 처리 처리 알림 Data 처리 처리 알림 Data 생성 consumer Data 저장 Data 생성 Data 저장 Data 생성 Data 확인 작업 의뢰 접수 알림 작업 의뢰 접수 알림
  • 57. import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class Main { public static final BlockingQueue<String> table = new LinkedBlockingQueue<String>(); public static void main(String[] args) { public static void main(String[] args) { new ProducerThread("mThread-1", 31415).start(); new ClientThread("Client-Thread").start(); } } public class ClientThread extends Thread { public ClientThread(String name) { super(name); } public void run() { try { while(true){ if(Main.table.getSize()>0) { new ConsumerThread().start(); }else{ Thread.sleep(1000); } } }catch(RejectedExecutionException e) { System.out.println( Thread.currentThread().getName() + ":" + e); }catch(CancellationException e) { System.out.println( Thread.currentThread().getName() + ":" + e); }catch(InterruptedException e) { System.out.println( Thread.currentThread().getName() + ":" + e); } } } Consumer Thread가 직접 일을 받아 처리하지 않고 해당 작업을 받아서 Consumer thread에게 분배하는 Thread를 생성하자.
  • 58. public class ClientThread extends Thread { ExecutorService executor; public ClientThread(String name, ExecutorService executor) { super(name); this.executor = executor; } public void run() { try { while(true){ if(Main.table.getSize()>0) { executor.execute(new ConsumerThread()); }else{ Thread.sleep(1000); } } } catch (RejectedExecutionException e) { System.out.println( Thread.currentThread().getName()+":"+e); } catch (CancellationException e) { System.out.println( Thread.currentThread().getName()+":"+e); } catch (InterruptedException e) { System.out.println( Thread.currentThread().getName()+":"+e); } } } import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); public static void main(String[] args) { new ProducerThread("MakerThread-1",31415).start(); new ClientThread("Cleint-Thread",executor).start(); } }
  • 59. 응답성을 높여 지연시간을 줄일 수 있다. 작업을 실행함과 동시에 작업 실행 여부를 Producer에게 알려줄 수 있다. VS 작업 결과에 Return 값을 받을 수 없다. 요청이 들어올 때마다 Thread가 생기기 때문에 메모리 부족이 발생할 수 있다. Thread로 실행하기 때문에 순차적으로 작업이 처리되지 않는다.
  • 61. public class ClientThread extends Thread { private final ExecutorService executorService; public ClientThread( ExecutorService executorService) { this.executorService = executorService; } public void run() { try { while( !Thread.currentThread().isInterrupted()) { while(Main.table.getSize()>0){ ConsumerThread request = new ConsumerThread(getName()); executorService.execute(request); } } } catch (RejectedExecutionException e) { System.out.println(getName() + " : " + e); } } } public class ConsumerThread extends Thread { private final String parentName; public ConsumerThread(String name) { parentName = name; } public void run() { try { String cake = Main.table.take(); if(cake != null) System.out.println( parentName + getName() + " eat " + cake); Thread.sleep(100); } catch (InterruptedException e) { } } }
  • 62. import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; public class Main { public static final BlockingQueue<String> table = new LinkedBlockingQueue<String>(); public static void main(String[] args) { new ProducerThread("MakerThread-1", 31415).start(); ExecutorService executorService = Executors.newFixedThreadPool(5); try { new ClientThread(executorService).start(); } finally { //executorService.shutdown(); } } } Thread-0Thread-1 eat [No.0] Thread-0Thread-2 eat [No.1] Thread-0Thread-3 eat [No.2] Thread-0Thread-4 eat [No.3] Thread-0Thread-5 eat [No.4] ……
  • 63. ExecutorService executorService=Executors.newFixedThreadPool(5); 처음부터 Thread 5개를 실행되어 더 이상 추가되거나 줄지 않는다. • Thread를 돌려가면서 사용하면 Thread를 새로 만들고 실행하는데 걸리는 시간을 줄여 성능을 향상시킬 수 있다. • 대신 Worker Thread의 처리능력을 초과하는 Request가 들어오는 경우 Channel에 Request가 늘어나게 되고 메모리 용량을 차지하게 된다.
  • 65. 응답성을 높여 지연시간을 줄일 수 있다. 작업을 실행함과 동시에 작업 실행 여부를 Producer에게 알려줄 수 있다. VS 작업 결과에 Return 값을 받을 수 없다. 요청이 들어올 때마다 Thread가 생기기 때문에 메모리 부족이 발생할 수 있다. Thread로 실행하기 때문에 순차적으로 작업이 처리되지 않는다.
  • 66. 응답성을 높여 지연시간을 줄일 수 있다. 작업을 실행함과 동시에 작업 실행 여부를 Producer에게 알려줄 수 있다. CPU가 할당받지 않는 유후 시간이 존재하는 상황이라면!!! 응답성 뿐 아니라 처리량 또한 비약적으로 향상된다!!!
  • 67. return 값을 준비하는 부분과 return 값을 이용하는 부분을 분리해서 구현해보자!! 작업 결과에 Return 값을 받을 수 없다????
  • 68. public class ConsumerThread extends Thread { public void run() { try { String cake = Main.table.take(); if(cake != null) System.out.println(getName() + " eat " + cake); Thread.sleep(100); } catch (InterruptedException e) { } } } public class ConsumerClientThread extends Thread { ExecutorService service; public ConsumerClientThread(String name, ExecutorService executorService) { super(name); service = executorService; } public void run() { try { while(true){ /*while(Main.table.getSize()>0){*/ service.execute(new ConsumerThread()); Thread.sleep(200); /*}*/ } } catch (RejectedExecutionException e) { System.out.println(Thread.currentThread().getName() + ":" + e); } catch (CancellationException e) { System.out.println(Thread.currentThread().getName() + ":" + e); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + ":" + e); } } }
  • 69. public class ProducerThread implements Callable<String> { private final Random random; private static int id = 0; public ProducerThread(long seed) { this.random = new Random(seed); } public String call() { String cake="[No." + nextId() + "]"; try { Thread.sleep(random.nextInt(1000)); Main.table.put(cake); } catch (InterruptedException e) { } return cake; } private static synchronized int nextId( ) { return id++; } } public class ProducerClientThread extends Thread { ExecutorService service; public ProducerClientThread(String name, ExecutorService executorService) { super(name); service = executorService; } public void run() { try { while(true){ Future<String> future = service.submit(new ProducerThread(31415)); Thread.sleep(10); String value = future.get(); System.out.println(Thread.currentThread().getName() + ": value = " + value); } } catch (RejectedExecutionException e) { System.out.println(Thread.currentThread().getName() + ":" + e); } catch (CancellationException e) { System.out.println(Thread.currentThread().getName() + ":" + e); } catch (ExecutionException e) { System.out.println(Thread.currentThread().getName() + ":" + e); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + ":" + e); } } }
  • 70. import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static final BlockingQueue<String> table = new LinkedBlockingQueue<String>(); public static void main(String[] args) { ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); ExecutorService eExecutorService = Executors.newFixedThreadPool(5); try { new ProducerClientThread("mThread-1", mExecutorService).start(); new ProducerClientThread("mThread-2", mExecutorService).start(); new ConsumerClientThread("eThread", eExecutorService).start(); Thread.sleep(2000); } catch (InterruptedException e) { } finally { System.out.println("*** shutdown ***"); makeExecutorService.shutdown(); eatExecutorService.shutdown(); } } } mThread-2: value = [No.0] Thread-0 eat [No.0] mThread-1: value = [No.1] Thread-1 eat [No.1] mThread-2: value = [No.2] Thread-2 eat [No.2] mThread-1: value = [No.3] Thread-3 eat [No.3] mThread-2: value = [No.4] Thread-4 eat [No.4] mThread-1: value = [No.5] Thread-5 eat [No.5] mThread-2: value = [No.6] Thread-6 eat [No.6] mThread-1: value = [No.7] Thread-7 eat [No.7] *** shutdown *** eThread:java.util.concurrent.RejectedExecutionException: Task Thread[Thread-10,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@7ec00b12[Shutting down, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 7] mThread-2: value = [No.8] Thread-8 eat [No.8] mThread-2:java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@2e85d205 rejected from java.util.concurrent.ThreadPoolExecutor@2bc62c46[Shutting down, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 9] mThread-1: value = [No.9] Thread-9 eat [No.9] mThread-1:java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@1dfec55f rejected from java.util.concurrent.ThreadPoolExecutor@2bc62c46[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 10]