Multi-thread : producer - consumer

71
Multi-Thread Producer-Consumer pattern

Transcript of Multi-thread : producer - consumer

Page 1: Multi-thread : producer - consumer

Multi-ThreadProducer-Consumer pattern

Page 2: Multi-thread : producer - consumer

Thread 를 알아야 하는 이유 ?

Page 3: Multi-thread : producer - consumer

한발 더 나아가 미래를 내다보는 개발자는 이미 단일 컴퓨터 내부에서의 병렬 처리를 넘어 여러 대의 컴퓨터에서 병렬로 동작하는 플랫폼을 찾고 있고 , 이와 같은 분산 병렬 처리 플랫폼이 한창 인기를 얻는 요즘입니다 . 이런 대규모 병렬 처리 플랫홈도 중요하긴 하지만 , 그 안에서는 항상 단일 프로세스 내부에서 동작하는 여러 스레드가 안정적으로 실행되도록 하는 병렬 처리 기법이 적용되어 있음을 잊어서는 안될 것입니다 .

-’ 멀티 코어를 100% 활용하는 자바 병렬 프로그래밍’ 옮김이의 말 중

Page 4: Multi-thread : producer - consumer

Thread 를 판단하는 기준 ??

Page 5: Multi-thread : producer - consumer

1. Safety2. Liveness3. Performance4. Reuse

Page 6: Multi-thread : producer - consumer

1. Safety

Page 7: Multi-thread : producer - consumer

Thread 에서 사용되는 값들의 무결성

정확성 !! Class 가 해당 Class 명세에 부합한다 .

해당 객체가 어떻게 생성되고 어떻게 사용되든 다른 작업 없이 ,정확하게 동작하면 해당 객체는 Thread Safe 하다고 말한다 .

Page 8: Multi-thread : producer - consumer

2. Liveness

Page 9: Multi-thread : producer - consumer

원하는 일이 원하는 시기에 동작한다 .

Deadlock 서로 다른 Thread 가 서로 가지고 있는 독점 자원을 원하여 멈춰져 있는 현상

Starvation Thread 가 자신이 원하는 자원을 할당받지 못하는 상황

Livelock 서로 다른 Thread 가 서로의 상태를 변경하면서 작업을 진행하지 못하는 현상

Page 10: Multi-thread : producer - consumer

3. Performance

Page 11: Multi-thread : producer - consumer

Muti-Thread 환경은 Single-Thread 환경보다 성능상 자원을 더 소모한다 .

( 락 , 메모리 , 상태 설정 , Thread 생성 및 제거 등등 )

Muti-thread 는 결국 !!

이런 비용을 지불하여 응답성과 처리용량을 늘리는 것이 목적 !!!

서로 다른 Thread 의 경쟁할 일이 없는 synchonized 블록의 경우 JVM 수준에서 동기화 관련 부하를 줄이거나 아예 없애주기 때문에 매우 빠르게 동작한다 . 실행 순서를 고정하는 역할을 할 수도 있다 .

대신 경쟁조건이 존재하는 경우 대부분의 JVM 이 운영체제의 기능을 호출한다 .

Page 12: Multi-thread : producer - consumer

4. Reuse

Page 13: Multi-thread : producer - consumer

Synchonized 키워드 , volatile 키워드• 성능을 위해서는 위에 두 키워드 뿐 아니라 Multi-thread 의 기법에

들어가는 모든 기술을 최소화하는 것이 좋다 .

하지만 !! 이런 키워드가 없는 경우• 후임 개발자가 해당 Class 에 대해 Thread safe 여부를 판단하기 힘들고

더 심한 경우에는 이미 안전한 Class 를 망칠 수도 있다 .

Page 14: Multi-thread : producer - consumer

Synchonized 키워드를 사용하지 않아도 되는 Class

Page 15: Multi-thread : producer - consumer

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 : BEGINPerson : ENDtime = 475msec.

SyncPerson : BEGINSyncPerson : ENDtime = 523msec.

Page 16: Multi-thread : producer - consumer

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 10000000Single Thread Person : END time = 534msec.Main Person : BEGINThread-0 Person : BEGIN 5000000Thread-1 Person : BEGIN 5000000Thread-0 Person : END time = 297msec.Thread-1 Person : END time = 298msec.Main Person : END time = 298msec.

Single Thread SyncPerson : BEGIN 10000000Single Thread SyncPerson : END time = 668msec.Main SyncPerson : BEGINThread-4 SyncPerson : BEGIN 5000000Thread-5 SyncPerson : BEGIN 5000000Thread-4 SyncPerson : END time = 1110msec.Thread-5 SyncPerson : END time = 1121msec.Main SyncPerson : END time = 1790msec.

Page 17: Multi-thread : producer - consumer

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());}bobpopbob

String 의 replace() 를 사용하면 실제 객체 값에는 영향을 주지 않고 그 순간만 데이터를 바꿔서 사용할 수 있다 .

Page 18: Multi-thread : producer - consumer

String 에 replace() 같은 메소드가 문제 !

다행히 String 에서는 별 문제 없지만…

Page 19: Multi-thread : producer - consumer

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 = bobPerson :: name = pop

StringBuffer 에서 제공하는 replace() 는 String 과 달 리 객 체 에 직 접 영 향 을 주 기 때 문 에 Muti-thread 환경에서 안전하지 못하다 .

Page 20: Multi-thread : producer - consumer

Thread 종료

Page 21: Multi-thread : producer - consumer

Thread 의 종료는 매우 중요하다 !!• Thread 가 정상적으로 종료하지 않으면 , -> JVM 역시 종료되지 않고 대기하게 된다 .

Page 22: Multi-thread : producer - consumer

실행 중인 Thread 를 그냥 종료 ???• 작업 중이던 메모리에 대한 반환 작업이 정상적으로 이뤄지지 않는 경우도

있고 해서 Thread.stop() 과 Thread.suspend() 와 같은 메소드는 사용하면 안된다 !!!(deprecated)

JAVA 에서는 특정 Thread 를 종료시키면 안된다 !!• 작업을 수행하는 Thread 와 해당 Thread 의 종료를 요청하는 Thread

를 같이 생각해야 한다 .

Page 23: Multi-thread : producer - consumer

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 는 작업을 정리한 후에 종료한다 .

Page 24: Multi-thread : producer - consumer

해당 Thread 가 sleep 이나 join 같은 대기 상태라면 ???

Page 25: Multi-thread : producer - consumer

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 가 아무런 영향도 미치지 못한다 .

Page 26: Multi-thread : producer - consumer

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); } } }}

Page 27: Multi-thread : producer - consumer

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 메소드를 사용하자 .

Page 28: Multi-thread : producer - consumer

notify vs notifyAll

Page 29: Multi-thread : producer - consumer

초기상태

실행

실행 가능

실행 상태

종료

sleepTIME_WATIN

GjoinTIME_WATIN

Gwait

TIME_WATING

BLOCKED

Thread 상태 변화 다이어그램Thread 생성

Start 메소드

schedule

Run 메소드 종료

GC

sleep

join

wait

Synchronized

I/O 대기 등 ..

interruptjoin 종료time out

interruptnotify/notifyAlltime out

interruptsleep 종료time out

Lock 획득

Page 30: Multi-thread : producer - consumer

notify 는 wait set 에서 대기하는 thread 중 하나만 실행하고notifyAll 은 wait set 에 들어가 있는 모든 thread 를 실행한다 .경쟁에서 밀린 thread 는 다시 wait set 으로 들어가게 된다 .

Page 31: Multi-thread : producer - consumer

그렇지만 notify 가 더 좋다고 할 수는 없다 .우선 notify 가 깨우는 thread 는 JVM 과 운영체제가 결정하고어떤 thread 가 동작하는지 알 수가 없다 . 그리고…

Page 32: Multi-thread : producer - consumer

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 는 동작하지 않는다 .

Page 33: Multi-thread : producer - consumer

Exception

Page 34: Multi-thread : producer - consumer

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 를 시켜줘야 한다 .

Page 35: Multi-thread : producer - consumer

Thread 가 동작할 준비가 되었는가 ??

Page 36: Multi-thread : producer - consumer

지나가기

Page 37: Multi-thread : producer - consumer

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 으로 처리할 수도 있다 .

Page 38: Multi-thread : producer - consumer

기다리기

Page 39: Multi-thread : producer - consumer

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 상태에서 벗어난 후에도 조건을 확인해야하기 때문 !!!

Page 40: Multi-thread : producer - consumer

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) { } }}

Page 41: Multi-thread : producer - consumer

BlockingQueue

Page 42: Multi-thread : producer - consumer

ConcurrentLinkedQueue

BlockingQueue 가 아니지만 내부 데이터 구조를 세션으로 분할하여 멀티쓰레드 상황에서 수행능력을 향상시킨다 .

BlockingQueue

java.util.Queue interface 의 sub-interface

offer 나 poll 이 아닌 고유의 put, take 를 통해 block 기능을 제공한다 .

ArrayBlockingQueue

요소의 상한이 있는 배열 베이스의 BlockingQueue

DelayQueue

Delayed 객체를 보관 .

지정 시간이 지나기 전에 take 할 수 없고 오래된 요소부터 take 한다 .

LinkedBlockingQueue

LinkedList 에 대응하는 BlockingQueue

Page 43: Multi-thread : producer - consumer

기다리다 지나가기

Page 44: Multi-thread : producer - consumer

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 가 들어오면 종료한다 .

Page 45: Multi-thread : producer - consumer

Producer-Consumer

Page 46: Multi-thread : producer - consumer

데이터 중심의 역할 분배 !!!• 응답성을 크게 향상시킬 수 있다 !

Page 47: Multi-thread : producer - consumer

producer queuecon-

sumer

Data 저장

Data 저장

Data 저장

Data 확인

Data 확인

Data 확인

Data 확인

Data 확인

Data 획득

Data 획득

Data 처리

Data 처리

처리 알림

처리 알림

Data 생성

Data 생성

Data 생성

Page 48: Multi-thread : producer - consumer

producer queuecon-

sumer

Data 저장

Data 저장

Data 저장

Data 확인

Data 확인

Data 획득

Data 획득

Data 처리

Data 처리

처리 알림

처리 알림

Data 생성

Data 생성

Data 생성

Data 확인

Data 획득 Data 처리

처리 알림

Page 49: Multi-thread : producer - consumer

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; }}

Page 50: Multi-thread : producer - consumer

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 nex-

tId() { 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)

{ } }}

Page 51: Multi-thread : producer - consumer

상태 변화가 없는 읽기에는 Lock 걸 필요가 있을까 ??

Page 52: Multi-thread : producer - consumer

  쓰기 읽기

쓰기 X X

읽기 X O

4 가지 경우 중 하나만 Lock 이 필요없지만… .

Page 53: Multi-thread : producer - consumer

쓰기와 읽기가 같은 빈도로 발생하지 않는다 !

만약 쓰기 thread 보다 읽기 thread 의 처리가 무겁거나 쓰기 threa 보다 읽기 threa 의 빈도가 더 높은 경우라면

읽기끼리 lock 해제하여 수행 능력을 높일 수 있다 !!!!

Java 에서는 ReentrantReadWriteLock class 에서 ReadLock, WriteLock 을 제공

Page 54: Multi-thread : producer - consumer

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(); } }}

Page 55: Multi-thread : producer - consumer

Consumer 의 위임

Page 56: Multi-thread : producer - consumer

producer table client

Data 저장

Data 확인

Data 획득

Data 획득

Data 처리

Data 처리

처리 알림

처리 알림

Data 생성

con-sumer

Data 저장

Data 생성

Data 저장

Data 생성

Data 확인

작업 의뢰

작업 의뢰

접수 알림

접수 알림

Page 57: Multi-thread : producer - consumer

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 를 생성하자 .

Page 58: Multi-thread : producer - consumer

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(); }}

Page 59: Multi-thread : producer - consumer

응답성을 높여 지연시간을 줄일 수 있다 .작업을 실행함과 동시에 작업 실행 여부를 Producer 에게 알려줄 수 있다 .

VS

작업 결과에 Return 값을 받을 수 없다 .요청이 들어올 때마다 Thread 가 생기기 때문에 메모리 부족이 발생할 수 있다 . Thread 로 실행하기 때문에 순차적으로 작업이 처리되지 않는다 .

Page 60: Multi-thread : producer - consumer

Consumer 의 대기

Page 61: Multi-thread : producer - 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) { } }}

Page 62: Multi-thread : producer - consumer

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]……

Page 63: Multi-thread : producer - consumer

ExecutorService executorService=Executors.newFixedThreadPool(5);처음부터 Thread 5 개를 실행되어 더 이상 추가되거나 줄지 않는다 .

• Thread 를 돌려가면서 사용하면 Thread 를 새로 만들고 실행하는데 걸리는 시간을 줄여 성능을 향상시킬 수 있다 .

• 대신 Worker Thread 의 처리능력을 초과하는 Request 가 들어오는 경우 Channel 에 Request 가 늘어나게 되고 메모리 용량을 차지하게 된다 .

Page 64: Multi-thread : producer - consumer

Future

Page 65: Multi-thread : producer - consumer

응답성을 높여 지연시간을 줄일 수 있다 .작업을 실행함과 동시에 작업 실행 여부를 Producer 에게 알려줄 수 있다 .

VS

작업 결과에 Return 값을 받을 수 없다 .요청이 들어올 때마다 Thread 가 생기기 때문에 메모리 부족이 발생할 수 있다 . Thread 로 실행하기 때문에 순차적으로 작업이 처리되지 않는다 .

Consumer 의 위임

Page 66: Multi-thread : producer - consumer

응답성을 높여 지연시간을 줄일 수 있다 .작업을 실행함과 동시에 작업 실행 여부를 Producer 에게 알려줄 수 있다 .

CPU 가 할당받지 않는 유후 시간이 존재하는 상황이라면 !!!

응답성 뿐 아니라 처리량 또한 비약적으로 향상된다 !!!

Page 67: Multi-thread : producer - consumer

return 값을 준비하는 부분과 return 값을 이용하는 부분을 분리해서 구현해보자 !!

작업 결과에 Return 값을 받을 수 없다 ????

Page 68: Multi-thread : producer - consumer

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); } }}

Page 69: Multi-thread : producer - consumer

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 nex-tId() { 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); } }}

Page 70: Multi-thread : producer - consumer

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.concurren-t.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 ja-va.util.concurrent.ThreadPoolExecutor@2bc62c46[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 10]

Page 71: Multi-thread : producer - consumer

감사합니다 .