Producer-Consumer模型的实现主要考虑一下几个方便:
- 生成者的实现
- 消费者的实现
- 消息队列
- 线程的通信、等待、同步
- 线程的终止
1.Producer
生产者负责把请求加入队列,如果队列已满则等待或者返回错误。
public class Producer{
// 消息队列
private LinkedList<Request> buffer = null;
// 队列的容量
private int maxBufferSize = 5;
private NormalLoggerI logger;
public Producer(LinkedList<Request> buffer, int maxBufferSize, NormalLoggerI logger) {
this.buffer = buffer;
this.maxBufferSize = maxBufferSize;
this.logger = logger;
}
public boolean add(Request request) {
try {
// 请求队列是竞争资源,只有一道进程可以读写
synchronized (buffer) {
while (buffer.size() == maxBufferSize) {
try {
logger.debug("buffer is full, Producer thread waiting for consumer to take something from buffer");
// 当请求队列为满的状态时,producer需要等待consumer从队列中取走请求
buffer.wait();
}
catch (Exception ex) {
logger.exception(ex);
}
}
buffer.add(request);
// 添加新的请求后通知所有的consumer
buffer.notifyAll();
}
return true;
}
catch (Exception ex) {
logger.error("Producer : fail to add request");
logger.exception(ex);
return false;
}
}
}
这里需要注意的是buffer作为多个线程共享的对象必须进行同步保护。否则buffer中请求的数量可能超过maxBufferSize。原因很简单,如果我们设定maxBufferSize = 5,此时buffer为空,此时此刻有六个producer通过访问buffer,那么他们读取buffer.size()的时候值都为0,于是6个producer同时执行add方法,最终buffer中请求的数量超过了5。
2. Consumer
消费者负责中队列中取走请求,如果队列为空消费者进入等待状态,直到新的请求到达。
public class Consumer extends Thread {
// 消息队列
private LinkedList<Request> buffer = null;
private NormalLoggerI logger;
public Consumer(LinkedList<Request> buffer, NormalLoggerI logger) {
this.buffer = buffer;
this.logger = logger;
}
@Override
public void run() {
while (true) {
try {
Request request = null;
synchronized (buffer) {
while (buffer.isEmpty()) {
logger.debug("Queue is empty, Consumer thread is waiting for producer thread to put something in queue");
try {
// 当消息队列为空时,消费者等待
buffer.wait();
// 如果client线程通过调用消费者线程的中断方法,并且测试队列为空,则消费者线程退出
if (buffer.size() == 0 && Thread.currentThread().isInterrupted()) {
logger.debug("Consumer thread is exit in the waiting");
return;
}
}
catch (InterruptedException ex) {
// 如果消费者线程在等待的过程中接收到来自主线程的中断请求,则消费者线程退出
logger.debug("Consumer thread is exit beacouse of InterruptedException");
return;
}
catch (Exception ex) {
logger.exception(ex);
}
}
// 从队列中获取一个请求
request = buffer.pop();
// 通知生产者进程:可以继续添加消息
buffer.notifyAll();
}
// Do Something
// 在处理完消息后如果消费者线程检测到中断状态,并且此时队列为空,则消费者线程立即退出
synchronized (buffer) {
if (buffer.size() == 0 && Thread.currentThread().isInterrupted()) {
logger.debug("Consumer thread is exit beacouse of Interrupte status");
return;
}
}
}
catch (Exception ex) {
logger.exception(ex);
}
}
}
}
3. Service
Service的作用是管理生产者、消费者、队列、以及服务的启动和停止。
需要注意的是生产者在添加消息之前必须检查服务是否被停止,如果停止则返回错误。
public class Service{
private static Producer producer = null;
private static List<Consumer> consumers = new ArrayList<Consumer>();
private static LinkedList<Request> buffer = new LinkedList<Request>();
// isServiceStopped对于Service来说是全局共享的所以标注为static
private static boolean isServiceStopped;
public static void init() {
producer = new Producer(buffer);
consumers.add(new Consumer(buffer)) ;
consumers.add(new Consumer(buffer)) ;
isServiceStopped = false;
// 启动所有的consumer
for (Consumer c : consumers) {
c.start();
}
}
public static boolean addRequest(String type, String text, String mobile) {
try {
// addReuest方法可能在多个client进程中被访问,所以必须使用同步机制,但是这会导致性能问题
synchronized(isServiceStopped) {
if (isServiceStopped) {
return false;
}
// 添加消息
Request request = new Request();
return producer.add(request);
}
}
catch (Exception ex) {
logger.exception(ex);
return false;
}
}
public static void stop() {
synchronized(isServiceStopped) {
// 发送中断请求
for (Consumer c : consumers) {
c.interrupt();
}
isServiceStopped = true;
}
}
}
4. 不足
4.1 Consumer的异常处理
在这个Producer-Consumer模型的实现版本中如果consumer在处理请求的过程中发生异常,我只做了记录日志的处理。如果能实现retry就更好了。
4.2 Service的新能瓶颈
你是否注意到在Service的视线中addRequest()和stop()方法都需要锁住isServiceStopped,而isServiceStopped是一个静态变量。也就是说在同一时刻所有的client线程中只有一道线程可以访问addRequest或者stop,这将导致client线程的等待,特别是在大并发的时候。
解决方法也很简单,可以使用volatile特性,这是一个轻量级的锁。