「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 7)
Producer-Consumer 패턴
producer(생산자)는 데이터를 작성하는 스레드, consumer(소비자)는 데이터를 이용하는 스레드. 생산자와 소비자가 서로 다른 스레드로 움직일 때, 양자의 처리 속도의 어긋남이 문제가 된다. 소비자가 데이터를 수신하려고 할 때 데이터가 아직 생성되지 않았거나 생산자가 데이터를 전달하려고 할 때 소비자가 데이터를 수신 할 수있는 상태가 아닐 수도 있습니다. Producer-Consumer 패턴에서는, 생산자와 소비자의 사이에 「교도역」이 들어간다. 이 다리 역할은 스레드 사이의 처리 속도의 어긋남을 채 웁니다. 생산자와 소비자 모두가 단수인 경우 파이프 패턴이라고 할 수 있습니다.
3명의 수탉이 케이크를 만들어 테이블에 놓고, 그것을 3명의 손님이 먹는, 테이블에는 3개까지 케이크를 둘 수 있다는 예를 생각한다. 테이블 위에 3개의 케이크가 놓여 있는데, 콕이 테이블에 더 케이크를 두려고 했다면, 콕은 테이블에 둘 곳이 생길 때까지 기다린다. 테이블 위에 1개도 케이크가 놓여 있지 않을 때, 손님이 테이블에서 케이크를 취하려고 하면, 손님은 케이크가 놓일 때까지 기다린다.
(코드 전체는 본서를 참조)
MakerThreadpublic class MakerThread extends Thread {
...
private static int id = 0; // ƒケーキ通し番号(全コックで共通)
...
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000));
String cake = "[ Cake No." + nextId() + " by " + getName() + " ]";
table.put(cake);
}
} catch (InterruptedException e) {
}
}
private static synchronized int nextId() {
return id++;
}
}
EaterThread
public class EaterThread extends Thread {
...
public void run() {
try {
while (true) {
String cake = table.take();
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
}
}
...
표public class Table {
private final String[] buffer;
private int tail; // 次にputする場所
private int head; // 次にtakeする場所
private int count; // buffer内のケーキ数
...
// ケーキを置く
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;
}
}
실행 결과...
MakerThread-1 puts [ Cake No.10 by MakerThread-1 ]
EaterThread-1 takes [ Cake No.10 by MakerThread-1 ]
MakerThread-1 puts [ Cake No.11 by MakerThread-1 ]
EaterThread-3 takes [ Cake No.11 by MakerThread-1 ]
MakerThread-3 puts [ Cake No.12 by MakerThread-3 ]
MakerThread-3 puts [ Cake No.13 by MakerThread-3 ]
EaterThread-2 takes [ Cake No.12 by MakerThread-3 ]
EaterThread-3 takes [ Cake No.13 by MakerThread-3 ]
...
등장인물
데이터 역할:
Producer 역에 의해 작성되어 Consumer 역에 의해 이용된다. 위의 예에서 케이크가 이것에 해당합니다.
Producer 역할:
데이터 역할을 만들고 채널 역할에 전달합니다. 위의 예에서는 MakerThread 클래스가 이것에 해당한다.
Consumer 역할:
Channel 역으로부터 Data 역을 받아 이용한다. 위의 예에서는, EaterThread 클래스가 이것에 해당한다.
채널 역할:
Producer 역으로부터 Data 역을 받아, 보관, Consumer 역으로부터의 요구에 응해 Data 역을 건네준다. Producer 역과 Consumer 역으로부터의 액세스에 대해서 배타 제어를 실시한다. Channel 역은 Producer 역과 Consummer 역 사이에 들어가 Data 역을 건네주는 중계 지점의 역할, 통신로의 역할을 한다. 위의 예에서는, Table 클래스가 이것에 해당한다.
생각을 넓히는 팁
public class MakerThread extends Thread {
...
private static int id = 0; // ƒケーキ通し番号(全コックで共通)
...
public void run() {
try {
while (true) {
Thread.sleep(random.nextInt(1000));
String cake = "[ Cake No." + nextId() + " by " + getName() + " ]";
table.put(cake);
}
} catch (InterruptedException e) {
}
}
private static synchronized int nextId() {
return id++;
}
}
public class EaterThread extends Thread {
...
public void run() {
try {
while (true) {
String cake = table.take();
Thread.sleep(random.nextInt(1000));
}
} catch (InterruptedException e) {
}
}
...
public class Table {
private final String[] buffer;
private int tail; // 次にputする場所
private int head; // 次にtakeする場所
private int count; // buffer内のケーキ数
...
// ケーキを置く
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;
}
}
...
MakerThread-1 puts [ Cake No.10 by MakerThread-1 ]
EaterThread-1 takes [ Cake No.10 by MakerThread-1 ]
MakerThread-1 puts [ Cake No.11 by MakerThread-1 ]
EaterThread-3 takes [ Cake No.11 by MakerThread-1 ]
MakerThread-3 puts [ Cake No.12 by MakerThread-3 ]
MakerThread-3 puts [ Cake No.13 by MakerThread-3 ]
EaterThread-2 takes [ Cake No.12 by MakerThread-3 ]
EaterThread-3 takes [ Cake No.13 by MakerThread-3 ]
...
InterruptedException이라는 예외가 throw될 가능성이 있다는 것은, 「시간이 걸리는」메소드인, 「캔슬할 수 있는」메소드라고 말할 수 있다.
thread가 wait로 기다리고 있는 경우에도, sleep와 같은 것으로 캔슬할 수 있다. interrupt 메소드를 사용하면, wait 하고 있는 thread에 대해서, 「이제 notify/notifyAll를 기다리지 않아도 된다. 그러나, wait의 경우에는, 락에 주의할 필요가 있다. 스레드는 가중치 세트에있을 때 일단 잠금을 해제했습니다. wait중에 interrupt를 불린 thread(즉, 캔슬 된 thread)는, 락을 재취득하고 나서 InterruptedException를 던진다. 즉, 락이 잡힐 때까지는 예외 InterruptedException를 던질 수 없다.
관련
「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 1)
「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 2)
「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 3)
「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 4)
「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 5)
「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 6)
Reference
이 문제에 관하여(「Java 언어로 배우는 디자인 패턴 (멀티 스레드 편)」정리 (그 7)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/yoshi-yoshi/items/1b6f4e0247496fdaac4c텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)