通过使用 notify 代替 notifyAll 减少线程竞争

Posted

技术标签:

【中文标题】通过使用 notify 代替 notifyAll 减少线程竞争【英文标题】:Reduce thread competition by using notify in place of notifyAll 【发布时间】:2021-12-21 06:42:17 【问题描述】:

我看到了这个自我实现的有界blocking queue。 对其进行了更改,旨在通过将 notifyAll 替换为 notify 来消除竞争。

但我不太明白添加的 2 个额外变量有什么意义:waitOfferCountwaitPollCount。 它们的初始值都是0。

添加前后的差异如下:Offer:

Poll:

我的理解是,这两个变量的目的是当对象上没有 wait 时,您不会进行无用的 notify 调用。但是如果不这样做会有什么危害呢? 另一个想法是它们可能与从notifyAllnotify 的切换有关,但我认为即使没有它们我们也可以安全地使用notify

完整代码如下:

class FairnessBoundedBlockingQueue implements Queue 
    protected final int capacity;

    protected Node head;

    protected Node tail;

    // guard: canPollCount, head
    protected final Object pollLock = new Object();
    protected int canPollCount;
    protected int waitPollCount;

    // guard: canOfferCount, tail
    protected final Object offerLock = new Object();
    protected int canOfferCount;
    protected int waitOfferCount;

    public FairnessBoundedBlockingQueue(int capacity) 
        this.capacity = capacity;
        this.canPollCount = 0;
        this.canOfferCount = capacity;
        this.waitPollCount = 0;
        this.waitOfferCount = 0;
        this.head = new Node(null);
        this.tail = head;
    

    public boolean offer(Object obj) throws InterruptedException 
        synchronized (offerLock) 
            while (canOfferCount <= 0) 
                waitOfferCount++;
                offerLock.wait();
                waitOfferCount--;
            
            Node node = new Node(obj);
            tail.next = node;
            tail = node;
            canOfferCount--;
        
        synchronized (pollLock) 
            ++canPollCount;
            if (waitPollCount > 0) 
                pollLock.notify();
            
        
        return true;
    

    public Object poll() throws InterruptedException 
        Object result;
        synchronized (pollLock) 
            while (canPollCount <= 0) 
                waitPollCount++;
                pollLock.wait();
                waitPollCount--;
            

            result = head.next.value;
            head.next.value = null;
            head = head.next;
            canPollCount--;
        
        synchronized (offerLock) 
            canOfferCount++;
            if (waitOfferCount > 0) 
                offerLock.notify();
            
        
        return result;
    

【问题讨论】:

不确定,但notifyAll 可能会向所有服务员发送资源可用的通知,然后所有服务员将竞争获取资源。 notify 只会唤醒众多服务员中的一个服务员。但是,我可能是错的。 【参考方案1】:

您需要询问该更改的作者,他们认为通过该更改可以实现什么。

我的看法如下:

notifyAll() 更改为notify() 是一件好事。如果有N 线程在队列的offerLockpollLock 上等待,那么这可以避免N - 1 不必要的唤醒。

似乎正在使用计数器避免在没有线程等待时调用notify()。这在我看来像是一个值得怀疑的优化。 AFAIK notify 在没有等待的情况下在互斥锁上非常便宜。所以这可能会产生很小的影响......但它不太可能是显着的。

如果您真的想知道,请编写一些基准测试。编写这个类的 4 个版本,没有优化,通知优化,计数器优化和两者。然后比较结果……针对不同级别的队列争用。


我不确定这里的“公平”应该是什么意思,但我在这个类中看不到任何东西来保证在 offerpoll 中等待的线程得到公平对待。

【讨论】:

【参考方案2】:

另一个想法是它们可能与从 notifyAll 到 notify 的切换有关,但我再次认为即使没有它们我们也可以安全地使用 notify?

是的,由于使用了两个锁(pollLock offerLock),所以在没有这两个变量的情况下将notyfiAll 更改为notify 是没有问题的。但如果你使用锁,你必须使用notifyAll

我的理解是,这 2 个变量的目的是当对象没有等待时,您不会进行无用的通知调用。但是如果不这样做会有什么坏处呢?

是的,这两个变量是为了避免无用的notify 调用。这两个变量还带来了额外的操作。我认为可能需要进行基准测试来确定不同场景下的性能。

另外,

1.作为阻塞队列,它应该实现接口BlockingQueuepolloffer方法都应该是non-blocking。它应该使用takeput

2.这不是Fairness 队列。

【讨论】:

以上是关于通过使用 notify 代替 notifyAll 减少线程竞争的主要内容,如果未能解决你的问题,请参考以下文章

为啥wait,notify和notifyAll必须在同步块或同步方法中调

尽量使用notifyAll,而不用notify

wait/notify/notifyAll使用

多线程-wait/notify/notifyAll

Java并发编程(10):使用wait/notify/notifyAll实现线程间通信的几点重要说明

转:Java并发编程之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明