定义:通过通道对数据(或任务)的生产者和消费者进行解耦,使二者不直接交互,从而使二者的处理速率相对来说互不影响。
Producer: 生产者,负责生产相应的“产品”并将其存入通道
service: 生产者对外暴露的服务方法
Product: 生产者所“生产”的数据或者任务
Channel: 对通道的抽象。通道充当生产者和消费者之间的缓冲区用于“产品”传递,它可以是有存储容量限制的。
put: 将“产品”存入通道
take: 从通道中取出一个“产品”
BlockingQuequChannel: 基于阻塞队列的Channel实现。
put: 将“产品”存入通道。当队列满时,该方法会将当前线程挂起直到队列非满
take: 从通道中取出一个“产品”。当队列为空时,该方法通常会将当前线程挂起直到队列非空
Consumer: 消费者,负责对“产品”进行处理
dispatch: 从通道中获取“产品”,并对其进行处理
生产者/消费者模式非常常见,不仅仅应用在多线程编程中。下列代码中BlockingQueueChannel缓存了一些来不及消费的产品,线程AbstractTerminatableThread充当了消费者,来为文件建立索引,这样产品的生产者和消费者在缓冲队列的帮助下,一定程度上实现了解耦,可以看作是非常简单版的消息中间件。
package com.bruce.producerConsumer;
/**
* @Author: Bruce
* @Date: 2019/6/4 16:10
* @Version 1.0
*/
public interface Channel<P> {
P take() throws InterruptedException;
void put(P product) throws InterruptedException;
}
package com.bruce.producerConsumer;
import java.util.concurrent.BlockingQueue;
/**
* @Author: Bruce
* @Date: 2019/6/4 16:16
* @Version 1.0
*/
public class BlockingQueueChannel<P> implements Channel<P> {
private final BlockingQueue<P> queue;
public BlockingQueueChannel(BlockingQueue<P> queue) {
this.queue = queue;
}
@Override
public P take() throws InterruptedException {
return queue.take();
}
@Override
public void put(P product) throws InterruptedException {
queue.put(product);
}
}
package com.bruce.producerConsumer;
import com.bruce.twoPhaseTermination.AbstractTerminatableThread;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.text.Normalizer;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author: Bruce
* @Date: 2019/6/4 16:00
* @Version 1.0
*/
public class AttachmentProcessor {
public static final String ATTACHMENT_STORE_BASE_DIR = "C:/";
private final Channel<File> channel = new BlockingQueueChannel<File>(
new ArrayBlockingQueue<File>(200)
);
private final AbstractTerminatableThread indexingThread =
new AbstractTerminatableThread() {
@Override
protected void doRun() throws Exception {
File file = null;
file = channel.take();
try {
indexFile(file);
} catch (Exception e) {
e.printStackTrace();
} finally {
terminationToken.reservations.decrementAndGet();
}
}
private void indexFile(File file) throws Exception {
Thread.sleep(1000);
}
};
public void init() {
indexingThread.start();
}
public void shutdown() {
indexingThread.terminate();
}
public void saveAttachment(InputStream in, String documentId, String originalFileName) throws IOException {
File file = saveAsFile(in, documentId, originalFileName);
try {
channel.put(file);
indexingThread.terminationToken.reservations.incrementAndGet();
} catch (InterruptedException e) {
;
}
}
private File saveAsFile(InputStream in, String documentId, String originalFileName) throws IOException {
String dirName = ATTACHMENT_STORE_BASE_DIR + documentId;
File dir = new File(dirName);
dir.mkdirs();
File file = new File(dirName + '/' + Normalizer.normalize(originalFileName, Normalizer.Form.NFC));
if (!new File(dirName).equals(new File(file.getCanonicalFile().getParent()))) {
throw new SecurityException("Invalid originalFileName:" + originalFileName);
}
try (InputStream dataIn = in) {
Files.copy(dataIn, Paths.get(file.getCanonicalPath()), StandardCopyOption.REPLACE_EXISTING);
}
return file;
}
}
并发情况下,会有大量的产品同时产生,也会有大量的产品同时消耗,生产者/消费者模式所构建的通道在一定程度上有削峰平谷的作用,有界阻塞队列的使用可以平衡生产者和消费者的处理能力,如果通道满了,那么生产者会被阻塞到消费者消费了一些产品,如果通道空了,那么消费者也会一直等待,直到有产品出现为止。除了使用有界阻塞队列构建通道,具有流量控制效果的无界阻塞队列也是可以达到相同的效果,例如LinkedBlockingQueue.
参考资料