为什么ReentrantLock with Conditions可以通过ArrayBlockingQueue中的两个线程获取

时间:2016-08-23 20:48:17

标签: java multithreading locking

这可能是一个愚蠢的问题,但我无法理解为什么这个类中的两个不同线程可以使用单个ReentrantLock(这是我用来玩线程和锁的ArrayBlockingQueue的简化解决方案):

class SampleThreadSafeQueue {

        private ReentrantLock lock = new ReentrantLock();
        private List<Integer> list = new ArrayList<>();
        private Condition notEmpty;
        private Condition notFull;
        private volatile int count;

        public SampleThreadSafeQueue() {
            notEmpty = lock.newCondition();
            notFull = lock.newCondition();
        }

        public int take() {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() +": acquired lock in take()");
                while (count == 0) {
                    notEmpty.await();
                }

                return extract();
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            } finally {
                System.out.println(Thread.currentThread().getName() +": released lock for take()");
                lock.unlock();
            }
        }

        private int extract() {
            int index = count <= 0 ? 0 : count - 1;
            Integer integer = list.get(index);
            list.remove(index);
            count--;
            list.clear();
            notFull.signal();
            return integer;
        }

        public void put(int value) {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() +": acquired lock in put()");
                while (!list.isEmpty()) {
                    notFull.await();
                }
                Thread.sleep(3000); // let's assume it takes 3 secs to add value
                insert(value);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() +": released lock for put()");
                lock.unlock();
            }
        }


        private void insert(int value) {
            list.add(value);
            count = list.size();
            notEmpty.signal();
        }
    }

我在控制台中收到此结果:

pool-1-thread-1: acquired lock in put()
pool-1-thread-1: released lock for put()
pool-1-thread-1: acquired lock in put()    - this guy has taken the lock
pool-1-thread-2: acquired lock in take()   - that guy has taken the lock as well! wtf?
pool-1-thread-2: released lock for take()
Value = 0
pool-1-thread-2: acquired lock in take()
pool-1-thread-1: released lock for put()
pool-1-thread-1: acquired lock in put()
pool-1-thread-2: released lock for take()
Value = 1
pool-1-thread-1: released lock for put()
pool-1-thread-1: acquired lock in put()
pool-1-thread-2: acquired lock in take()
pool-1-thread-2: released lock for take()
Value = 2
...

我怀疑这是因为我在循环中使用的条件,但逻辑上无法理解为什么会发生这种情况。 感谢您的解释。谢谢!

2 个答案:

答案 0 :(得分:1)

当您从某个锁定获得的await()上调用Condition时,您的线程会释放锁定并停止,直到从另一个线程通知Condition(通过{{ 1}}或signal())。

因此,您的线程1获取锁,但随后调用signalAll()并切换到等待模式,释放最近由线程2获取的锁。当线程2完成时,它通知线程1(通过调用await())并释放锁。锁1会立即被线程1重新获取,它会唤醒并继续执行其工作并最终释放锁。

然后他们以稍微不同的顺序重复它。

答案 1 :(得分:1)

查看条件文档http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Condition.html

您需要了解的是条件(等待,信号)就像同步(等待,通知)。调用await()将释放对锁定的保留。 (你仍然需要致电lock.unlock() tho)。函数put()等待take()触发notFull.signal()take()等待put()触发notEmpty.signal()。锁定最终会直接在await()来电同步,而不会像您习惯的那样lock.lock()同步。

这是记录在案的例子:

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();
     }
   }
 }