生产消费实现-写代码

182 阅读3分钟

定义

  • 生产者持续生产,直到缓冲区满,阻塞;缓冲区不满后,继续生产
  • 消费者持续消费,直到缓冲区空,阻塞;缓冲区不空后,继续消费
  • 生产者可以有多个,消费者也可以有多个

实现手段

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

代码已完整,文字描述待续。。。