以生产者/消费者模式暂停消费者

时间:2015-07-22 13:12:08

标签: java multithreading producer-consumer blockingqueue

我的生产者和消费者与BlockingQueue有关。

消费者从队列中等待记录并处理它:

Record r = mQueue.take();
process(r);

我需要从其他线程暂停这个过程一段时间。如何实现呢?

现在我认为实现它,但它看起来像是一个糟糕的解决方案:

private Object mLock = new Object();
private boolean mLocked = false;

public void lock() {
    mLocked = true;
}

public void unlock() {
    mLocked = false;
    mLock.notify();

}

public void run() {
    ....
            Record r = mQueue.take();
            if (mLocked) {
                mLock.wait();
            }
            process(r);
}

3 个答案:

答案 0 :(得分:2)

您可以使用java.util.concurrent.locks.Condition Java docs根据相同条件暂停一段时间。

这种方法看起来很干净, ReentrantLock机制的吞吐量比同步更好。请阅读以下IBM article

的摘录
  

作为奖励,ReentrantLock的实施更具可扩展性   争用比当前执行同步。 (它   可能会对竞争性能进行改进   在未来版本的JVM中同步。)这意味着什么时候   许多线程都争夺同一个锁,总数   ReentrantLock的吞吐量通常会好于   同步。


BlockingQueue以解决生产者 - 消费者问题而闻名,它还使用Condition进行等待。

请参阅以下来自Java doc' s Condition的示例,该示例是生产者 - 消费者模式的示例实现。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }

进一步阅读:

答案 1 :(得分:2)

我认为您的解决方案简单而优雅,并认为您应该对其进行一些修改。我建议的修改是synchronization

没有它,线程干扰和内存一致性错误可能(并且经常会发生)。最重要的是,你不能拥有waitnotify你不拥有的锁(如果你在synchronized区块中拥有它,你就拥有它...)。修复很简单,只需在等待/通知的位置添加mLock同步块。此外,当您从其他主题更改mLocked时,您需要将其标记为volatile

private Object mLock = new Object();
private volatile boolean mLocked = false;

public void lock() {
    mLocked = true;
}

public void unlock() {
    synchronized(mlock) {
        mLocked = false;
        mLock.notify();
    }

}

public void run() {
    ....
            Record r = mQueue.take();
            synchronized(mLock) {
                while (mLocked) {
                    mLock.wait();
                }
            }
            process(r);
}

答案 2 :(得分:1)

创建一个扩展BlockingQueue实现的新类。添加两个新方法pause()unpause()。如果需要,请考虑paused标记并使用其他blockingQueue2等待(在我的示例中仅使用take()方法,而不是put()):

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("lock")
    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());//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.
}