为什么这个LinkedList被锁定在空的while循环中?

时间:2016-05-23 15:41:25

标签: java multithreading jvm

问题解决了!感谢你们。请在下面自己查看答案

==================

在我写的一个方法中:

while (queueP.isEmpty()) ;

queueP是一个LinkedList,还有另一个线程试图锁定它并写入它。我想确保queueP先不为空,然后再去。但代码最终成为一个死循环。 jstack显示这个while循环锁定了queueP所以写入线程总是等待锁定它,并且没有任何东西写入queueP并且循环永远不会结束。

这是jstack结果中的内容:

"c2-0" #11 prio=5 os_prio=0 tid=0x000000001d51b800 nid=0x46a0 runnable [0x000000001de4e000]
   java.lang.Thread.State: RUNNABLE
    at dbq.DoubleBufferQueue.take(DoubleBufferQueue.java:44)
    - locked <0x000000076f2083b8> (a java.util.LinkedList)
    at dbq.Test$ToyConsumer.run(Test.java:91)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - None

"p2-0" #10 prio=5 os_prio=0 tid=0x000000001d519800 nid=0xbc4 waiting for monitor entry [0x000000001dd4e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at dbq.DoubleBufferQueue.put(DoubleBufferQueue.java:33)
    - waiting to lock <0x000000076f2083b8> (a java.util.LinkedList)
    at dbq.Test$ToyProducer.run(Test.java:58)
    at java.lang.Thread.run(Thread.java:745)

   Locked ownable synchronizers:
    - None

DoubleBufferQueue.java:44是while循环。

那么,为什么这个列表在while循环中被锁定?它是JVM的某种功能吗?

=============================================== =======

put()take()是:

 @Override
    public void put(T e) throws InterruptedException {
        // TODO Auto-generated method stub
        synchronized (queueP) {
            queueP.offer(e);
        }
    }

    @Override
    public T take() throws InterruptedException {
        // TODO Auto-generated method stub
        synchronized (queueC) {
            if (queueC.isEmpty()) {             
                while (queueP.isEmpty()) ;
                synchronized (queueP) {
                    Queue<T> tmp = queueC;
                    queueC = queueP;
                    queueP = tmp;
                }
            }
            return queueC.poll();
        }
    }

我今天读到了关于双缓冲区队列的问题,并且我试图自己编写一个(有人可以告诉我这是否是正确的名称?)。这只是一个简单的演示。我知道take()中的切换看起来非常危险,而且ReentrantLock会好得多,但我只是好奇......所以我只创建了一个消费者和一个生产者,但问题仍然存在。

jstack是否显示列表锁定的确切行?我对此并不了解。第44行只是while循环。

2 个答案:

答案 0 :(得分:3)

Read the Javadoc

  

请注意,此实施未同步。如果多个线程同时访问链表,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。

如果没有同步,则无法保证在输入循环后将重新检查queueP.isEmpty()的值。

正如@Mena建议的那样,您应该在java.util.concurrent包中查看专为并发访问而设计的集合。

答案 1 :(得分:0)

现在我知道为什么它有时会陷入死循环。

关键是,尝试锁定对象的第一个线程不一定首先获得锁定。只有当两个操作符合&#34; Happens-Before&时才会重新排序。 #34;关系。

所以这就是在死循环开始之前发生的事情,给定一个消费者线程&#34; c2-0&#34;和生产者线程&#34; p2-0&#34;。

  1. c2-0锁定了queueC和queueP,但它在queueC = queueP之后和queueP = tmp;之前被暂停。
  2. p2-0 put()试图锁定queueP,暂停。
  3. c2-0恢复并完成了take()的此次通话。很快c2-0开始下一个taken(),试图锁定队列C,这也是p2-0所需要的,因为当p2-0卡住时,queueC和queueP都引用相同的LinkedList。
  4. c2-0在p2-0之前获得了锁定,但稍后需要。消费者开始了死循环。
  5. 我从中学到了什么:

    1. ReetrantLock是一个比synchronized更好的选择,特别是当引用可能被更改时。
    2. 从不忘记&#34;发生之前&#34;并且绝不假设线程之间有任何其他顺序
    3. jstack剂量并不意味着while(queueP.isEmpty())锁定了列表。事实上它的&#39;结果说&#34;线程c2-0锁定队列P并且它现在在第44行运行&#34;这完全是无关紧要的。真是个误会......