生產消費實現-寫程式碼

單同志發表於2019-08-23

定義

  • 生產者持續生產,直到緩衝區滿,阻塞;緩衝區不滿後,繼續生產
  • 消費者持續消費,直到緩衝區空,阻塞;緩衝區不空後,繼續消費
  • 生產者可以有多個,消費者也可以有多個

實現手段

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();
        }
    }
}
複製程式碼

程式碼已完整,文字描述待續。。。

相關文章