每個鎖建立多個條件佇列以避免虛假喚醒

banq發表於2019-05-25

多個條件佇列以實現更好的併發性。每個鎖使用單獨的條件佇列的優點。
  • 它避免了虛假的喚醒和上下文切換。例如,如果您使用notifyAll進行傳統等待,則最終會喚醒正在等待不同條件的執行緒。
  • 當您在單獨的條件佇列上等待時,您可以使用signal 而不是signalAll來進一步提高效能。

以下是在無界佇列之上的有界BlockingQueue的兩個經典實現。

每個鎖具有單獨的等待集

 public class BlockingQueue<T> {

    private final Queue<T> queue;
    private final Lock lockObj = new ReentrantLock();
    private final Condition empty = lockObj.newCondition();
    private final Condition full = lockObj.newCondition();
    private int maxLength;
    private int currentSize = 0;

    public BlockingQueue(int maxLength) {
      this.queue = new ArrayDeque<T>();
      this.maxLength = maxLength;
    }

    public void offer(T elem) throws InterruptedException {
      lockObj.lock();
      try {
        while (currentSize == maxLength) {
          full.await();
        }
        queue.offer(elem);
        currentSize++;
        empty.signal();
      } finally {
        lockObj.unlock();
      }
    }

    public T poll() throws InterruptedException {
      lockObj.lock();
      try {
        while (currentSize == 0) {
          empty.await();
        }
        T elem = queue.poll();
        currentSize--;
        full.signal();
        return elem;
      } finally {
        lockObj.unlock();
      }
    }
  }


使用JMH測試吞吐量:

Benchmark                           Mode  Cnt         Score        Error  Units
BenchmarkBlockingDequeu.testProduceAndConsume   thrpt   25     12500542.933      ± 374127.076 ops/s


舊的方式(單鎖和等待)

 public class BlockingQueueWait<T> {


    private final Queue<T> queue;
    private final Object lockObj = new Object();
    private int maxLength;
    private int currentSize = 0;

    public BlockingQueueWait(int maxLength) {
      this.queue = new ArrayDeque<T>();
      this.maxLength = maxLength;
    }

    public void offer(T elem) throws InterruptedException {
      synchronized (lockObj) {
        while (currentSize == maxLength) {
          lockObj.wait();
        }
        queue.offer(elem);
        currentSize++;
        lockObj.notifyAll();
      }
    }

    public T poll() throws InterruptedException {
      synchronized (lockObj) {
        while (currentSize == 0) {
          lockObj.wait();
        }
        T elem = queue.poll();
        currentSize--;
        lockObj.notifyAll();
        return elem;
      }
    }
  }

使用JMH測試吞吐量:

Benchmark                         Mode  Cnt        Score       Error  Units
BenchMarkBlockingWait.testProduceAndConsume  thrpt   25     2702842.067    ± 24534.073  ops/s


如果你仔細看看上面的實現,ops /s的差異是巨大的,其中大部分是由虛假的喚醒引起的,並且沒有使用顯式條件佇列和每個鎖的等待集,你最終會浪費寶貴的cpu週期。因此,如果您正在編寫併發庫實現,請記住您有更好的併發支援,並且您可以為每個鎖建立多個條件佇列以避免虛假喚醒。
 

相關文章