用两把锁守卫状态

时间:2015-08-07 14:17:10

标签: java multithreading concurrency java.util.concurrent

作为an answerquestion about pausing a BlockingQueue,我想到了使用现有的阻塞结构blockingQueue2并使用两个不同的锁来保护状态。

public class BlockingQueueWithPause<E> extends LinkedBlockingQueue<E> {

    private static final long serialVersionUID = 184661285402L;

    private Object lock1 = new Object();//used in pause() and in take()
    private Object lock2 = new Object();//used in pause() and unpause()

    //@GuardedBy("lock1")
    private volatile boolean paused;

    private LinkedBlockingQueue<Object> blockingQueue2 = new LinkedBlockingQueue<Object>();

    public void pause() {
        if (!paused) {
            synchronized (lock1) {
            synchronized (lock2) {
                if (!paused) {
                    paused = true;
                    blockingQueue2.removeAll();//make sure it is empty, e.g after successive calls to pause() and unpause() without any consumers it will remain unempty
                }
            }
            }
        }
    }

    public void unpause() throws InterruptedException {
        if (paused) {
            synchronized (lock2) {
                paused = false;
                blockingQueue2.put(new Object());//will release waiting thread, if there is one
            }
        }
    }

    @Override
    public E take() throws InterruptedException {
        E result = super.take();

        if (paused) {
            synchronized (lock1) {//this guarantees that a single thread will be in the synchronized block, all other threads will be waiting
                if (paused) {
                    blockingQueue2.take();
                }
            }
        }

        return result;
    }

    //TODO override similarly the poll() method.
}

我需要两个不同的锁,否则unpause()可以等待消费者线程已经在lock1中保留的take()

我的问题:

  1. 这会陷入僵局吗?
  2. 它有用吗?
  3. 你经常看到这样的代码,因为我自己发现它不可读?
  4. 我应该如何注释paused标志:@GuardedBy("lock1, locks2")
  5. PS:欢迎任何改进(除此之外我可以使用二进制信号量而不是blockingQueue2)。

1 个答案:

答案 0 :(得分:2)

我会逐一回答你的问题

  

这会陷入僵局吗?

不,你不会导致死锁。如果您以不同的顺序获得lock1lock2,那么可能会导致死锁。因为你拿着它们时按照相同的顺序获得它们就应该没问题。

  

它有用吗?

似乎。所有事情都发生在订购之前似乎很满意。

  

你经常看到这样的代码,因为我自己发现它不可读?

我以前从未见过这种实现方式。我同意它不是很优雅。

我将提出一个使用Phaser的替代解决方案。有人可以说这不是更优雅,只是另一种方法。我已经审查了一段时间,我认为这已经足够了。当然,我从来没有见过这种方法,但想起来很有趣。

public static class BlockingQueueWithPause<E> extends LinkedBlockingQueue<E> {

    private static final long serialVersionUID = 184661285402L;

    private final Phaser phaser = new Phaser(1);
    private volatile int phase = phaser.getPhase();

    public BlockingQueueWithPause() {
        // base case, all phase 0 await's will succeed through.
        phaser.arrive();
    }

    public void pause() {
        phase = phaser.getPhase();
    }

    public void unpause() throws InterruptedException {
        phaser.arrive();
    }

    @Override
    public E take() throws InterruptedException {
        phaser.awaitAdvance(phase);

        E result = super.take();

        return result;
    }
}

我想我应该解释这个解决方案。如果CylicBarrierCountDownLatch有一个孩子,Phaser就像。它允许重新使用屏障,而不是等待屏障跳闸。

在基本情况下,共享phase将为0.由于arrive在构造函数中被调用,phaser的内部阶段为1.因此,如果{{1}在没有调用take的情况下调用pause将被调用0.由于内部阶段为1,因此移相器快速路径输出并且是一个简单的易失性负载(0阶段已经发生,所以我们不再需要等待提前)。

如果调用了awaitAdvance,则共享的pause变量将更新为移相器的内部阶段,现在为1.因此,phasetake在{1}}上导致它暂停

awaitTermination到达将导致所有线程unpause释放并将移相器的内部阶段递增到2.再次,后续的接收将快速路径而没有相应的暂停。