定义
- 生产者持续生产,直到缓冲区满,阻塞;缓冲区不满后,继续生产
- 消费者持续消费,直到缓冲区空,阻塞;缓冲区不空后,继续消费
- 生产者可以有多个,消费者也可以有多个
实现手段
LinkedBlockingQueue/wait,notify/Lock,Condition
面向接口编程,抽象生产消费
public interface Consumer {
void consume() throws InterruptedException;
}
public interface Produce {
void produce() throws InterruptedException;
}
// 可以省略上面接口,直接定义此抽象类。
abstract class AbsConsumer implements Consumer, Runnable {
@Override
public void run () {
try {
consume();
} cath (InterruptedException) {
}
}
}
abstract class AbsProducer implements Consumer, Runnable {
@Override
public void run () {
try {
consume();
} cath (InterruptedException) {
}
}
}
不同的Model,会有不同的生产,消费实现。我们可以再抽象下model。
当然也可以不,在一个model里面实现。
public interface Model {
Runnable newRunnableConsumer();
Runnable newRunnableProducer();
}
定义消费单位
public class Task {
public int no;
public Task(int no) {
this.no = no;
}
}
具体生产消费代码
// 只要改变下具体的实现Model就可以了。
// 这里完全可以用一个策略模式,动态设置model。
// 但这是测试生产消费代码,就不要那么正式了。
Model model = new BlockingQueueModel(3);
for (int i = 0; i < 2; i++) {
new Thread(model.newRunnableConsumer()).start();
}
for (int i = 0; i < 5; i++) {
new Thread(model.newRunnableProducer()).start();
}
LinkedBlockingQueue
这是天生的一个为我们生产消费实现的数据结构。它提供了并发,容量的实现。接下来,一波代码
public class BlockingQueueModel implements Model {
//存储我们的Task
private final BlockingQueue<Task> queue;
// 生产Task,原子性保证每个Task的标识在并发下的准确性,互相不冲突
private final AtomicInteger increTaskNo = new AtomicInteger(0);
// 构造方法提供容量控制参数
public BlockingQueueModel(int cap) {
// 使用LinkedBlockingQueue和ArrayBlocingQueue操作效率上没有太大差别
this.queue = new LinkedBlockingQueue<>(cap);
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl extends AbstractConsumer{
@Override
public void consume() throws InterruptedException {
Thread.sleep((long) (Math.random() * 1000));
Task task = queue.take();
System.out.println("consume: " + task.no);
}
}
private class ProducerImpl extends AbstractProducer{
@Override
public void produce() throws InterruptedException {
Thread.sleep((long) (Math.random() * 1000));
Task task = new Task(increTaskNo.getAndIncrement());
System.out.println("produce: " + task.no);
queue.put(task);
}
}
}
wait,notify
public class WaitNotifyModel implements Model {
// object去提供锁
private final Object BUFFER_LOCK = new Object();
// 存储Task,用object保证并发性
private final Queue<Task> buffer = new LinkedList<>();
private final int cap;
private final AtomicInteger increTaskNo = new AtomicInteger(0);
public WaitNotifyModel(int cap) {
this.cap = cap;
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl extends AbstractConsumer{
@Override
public void consume() throws InterruptedException {
synchronized (BUFFER_LOCK) {
// 缓冲区为空,阻塞。这种阻塞属于等待阻塞。
while (buffer.size() == 0) {
BUFFER_LOCK.wait();
}
// 缓冲区的操作得到synchronized的同步保证。
Task task = buffer.poll();
Thread.sleep((Math.random() * 1000));
System.out.println("consume: " + task.no);
BUFFER_LOCK.notifyAll();
}
}
}
private class ProducerImpl extends AbstractProducer{
@Override
public void produce() throws InterruptedException {
Thread.sleep((long) (Math.random() * 1000));
synchronized (BUFFER_LOCK) {
while (buffer.size() == cap) {
BUFFER_LOCK.wait();
}
Task task = new Task(increTaskNo.getAndIncrement());
buffer.offer(task);
System.out.println("produce: " + task.no);
BUFFER_LOCK.notifyAll();
}
}
}
}
synchronized控制produce,consume并发,wait控制缓冲区临界阻塞。 所以,你会看到produce,consume其实是串行的。同一时间,要么是在produce,要么是在consume。那么,问题来了:为什么不可以并行?通过什么方案可以实现produce,consume的并行并且可以保证缓冲区临界值的准确控制?
Lock,Condition
对于wait,notify的问题,在我们的Lock,Condition这边可以得到解决!其实,参考的就是LinkedBlockingQueue的实现。
public class LockConditionModel implements Model{
private final int cap;
// 读写锁分离。
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
// 这个保证临界区的值
private final AtomicInteger count = new AtomicInteger();
private Queue<Task> buffer = new LinkedList<>();
public LockConditionModel(int cap) {
this.cap = cap;
}
@Override
public Runnable newRunnableConsumer() {
return new ConsumerImpl();
}
@Override
public Runnable newRunnableProducer() {
return new ProducerImpl();
}
private class ConsumerImpl extends AbstractConsumer{
@Override
public void consume() throws InterruptedException {
Task x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
// 这个while不可以换成if。
// 假设有两个consume线程卡在这里。前一个获取锁,操作后count为零,那么下一个如果不再次判断就出问题啦
while (count.get() == 0) {
notEmpty.await();
}
x = buffer.poll();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
}
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
private class ProducerImpl extends AbstractProducer {
@Override
public void produce() throws InterruptedException {
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
Task task = buffer.poll();
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
}
代码已完整,文字描述待续。。。