Java中一个LinkedBlockingQueue源码的问题

来源:互联网 发布:农村淘宝政策 编辑:程序博客网 时间:2024/05/02 03:35
在看LinkedBlockingQueue源码的时候发现了一个问题。一个BlockingQueue实际上是由一个链表以及对应的put和poll方法组成的。
下面列出了源码中的put方法。
public void put(E e) throws InterruptedException {        if (e == null) throw new NullPointerException();        int c = -1;        Node<E> node = new Node(e);        final ReentrantLock putLock = this.putLock;        final AtomicInteger count = this.count;        putLock.lockInterruptibly();        try {            while (count.get() == capacity) {                notFull.await();            }            enqueue(node);            c = count.getAndIncrement();            if (c + 1 < capacity)                notFull.signal();        } finally {            putLock.unlock();        }        // 这里通过判断c是否为0来决定是否唤醒notEmpty        // c是之前锁putLock时获得的变量,takeLock并没有锁        // c是否可能会是过期变量呢?        if (c == 0)            signalNotEmpty();    }


问题就在最后的这个signalNotEmpty()上了,这个方法的功能是解开notEmpty这个Condition(Condition是ReentrantLock中的概念,也就是对象锁),代码如下:
    private void signalNotEmpty() {        final ReentrantLock takeLock = this.takeLock;        takeLock.lock();        try {            notEmpty.signal();        } finally {            takeLock.unlock();        }    }
功能很简单,先锁住takeLock,然后唤醒takeLock中notEmpty这个Condition。
put方法中如果c为0(c的意思是put调用之前的QueueCount)就调用signalNotEmpty(),但是判断if (c == 0)这句话时却并没有加任何的锁,也就是说别的线程完全有机会在put执行到c = count.getAndIncrement()之后将queue又一次取空,而put方法的最后只是通过判断已经过时的c变量就将notEmpty解开了。有可能导致notEmpty的误唤醒。然后来看一下take方法是否做了保护。
    public E take() throws InterruptedException {        E x;        int c = -1;        final AtomicInteger count = this.count;        final ReentrantLock takeLock = this.takeLock;        takeLock.lockInterruptibly();        try {            while (count.get() == 0) {                // 这里如果count为0就await,                // 但是就像之前说的,可能被误唤醒                notEmpty.await();            }            // 如果实际queue里什么都没有,那么就有问题了            x = dequeue();            c = count.getAndDecrement();            if (c > 1)                notEmpty.signal();        } finally {            takeLock.unlock();        }        if (c == capacity)            signalNotFull();        return x;    }
实际的模型是这样的,线程A、B、C。A是put的,B、C是take的。
首先count是0,A开始put,
A执行到将count增加为1的时候B拿走了这个元素,然后C开始take,此时C在判断notEmpty的时候block住了。
这个时候A误将notEmpty解锁。C被唤醒,同时队列也是空的。
这种微妙的巧合。因为然是有takeLock锁的,所以B和C虽是两个线程,其实也是串行。实际出问题的可能性就是A线程只执行两行方法的时间,B和C两个串行的线程却执行了超多步骤。
原创粉丝点击