作为an answer的question 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()
。
我的问题:
paused
标志:@GuardedBy("lock1, locks2")
? PS:欢迎任何改进(除此之外我可以使用二进制信号量而不是blockingQueue2
)。
答案 0 :(得分:2)
我会逐一回答你的问题
这会陷入僵局吗?
不,你不会导致死锁。如果您以不同的顺序获得lock1
和lock2
,那么可能会导致死锁。因为你拿着它们时按照相同的顺序获得它们就应该没问题。
它有用吗?
似乎。所有事情都发生在订购之前似乎很满意。
你经常看到这样的代码,因为我自己发现它不可读?
我以前从未见过这种实现方式。我同意它不是很优雅。
我将提出一个使用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;
}
}
我想我应该解释这个解决方案。如果CylicBarrier和CountDownLatch有一个孩子,Phaser就像。它允许重新使用屏障,而不是等待屏障跳闸。
在基本情况下,共享phase
将为0.由于arrive
在构造函数中被调用,phaser
的内部阶段为1.因此,如果{{1}在没有调用take
的情况下调用pause
将被调用0.由于内部阶段为1,因此移相器快速路径输出并且是一个简单的易失性负载(0阶段已经发生,所以我们不再需要等待提前)。
如果调用了awaitAdvance
,则共享的pause
变量将更新为移相器的内部阶段,现在为1.因此,phase
将take
在{1}}上导致它暂停
awaitTermination
到达将导致所有线程unpause
释放并将移相器的内部阶段递增到2.再次,后续的接收将快速路径而没有相应的暂停。