2010-02-27 95 views
7

我一直在阅读Doug Lea的“Java中的并发编程”一书。您可能知道,Doug最初编写了Java Concurrency API。但是,有些事情使我感到困惑,我希望就这个小难题获得一些我的看法!同步队列

看看下面的代码Doug Lea的排队例子...

class LinkedQueue { 
    protected Node head = new Node(null); 
    protected Node last = head; 

    protected final Object pollLock = new Object(); 
    protected final Object putLock = new Object(); 

    public void put(Object x) { 
    Node node = new Node(x); 
    synchronized (putLock) {  // insert at end of list 
     synchronized (last) { 
     last.next = node;  // extend list 
     last = node; 
     } 
    } 
    } 

    public Object poll() {   // returns null if empty 
    synchronized (pollLock) { 
     synchronized (head) { 
     Object x = null; 
     Node first = head.next; // get to first real node 
     if (first != null) { 
      x = first.object; 
      first.object = null; // forget old object 
      head = first;   // first becomes new head 
     } 
     return x; 
     } 
    } 
    } 

    static class Node {   // local node class for queue 
    Object object; 
    Node next = null; 

    Node(Object x) { object = x; } 
    } 
} 

这是一个相当不错的队列。它使用两台显示器,因此生产者和消费者可以同时访问队列。太好了!然而,'last'和'head'的同步让我很困惑。该书指出这是队列目前或将要有0个条目的情况所需要的。好吧,够公平的,这是有道理的。

不过,后来我看了一下Java Concurrency LinkedBlockingQueue。队列的original版本在头部或尾部不同步(我也想发布另一个链接到现代版本,它也遭受同样的问题,但我不能这样做,因为我是一个新手)。我想知道为什么不呢?我在这里错过了什么吗?是否存在我缺少的Java内存模型的某些特殊性质?我会想到可见性的目的,这种同步是必要的?我会感谢一些专家意见!

+0

请注意,我目前的理论是关于'signalNotEmpty'方法进入'takeLock'的synchronized块。这也许会迫使“头”被看到。 – 2010-02-27 12:14:22

回答

2

在您为最新JRE放置链接以及版本的版本中,Node类中的项目是不稳定的,它强制读取和写入对所有其他线程都可见,这是一个更深入的解释http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile

+0

谢谢,是的,我现在看到。写入volatile变量的效果与释放同步监视器的效果相同,因此解决了可见性问题。 – 2010-03-12 19:51:48

2

这里的微妙之处是synchronized(null)会抛出一个NullPointerException,所以head和last都不允许为null。它们都被初始化为永远不会从任一列表返回或删除的相同虚拟节点的值。

put()和poll()在两个不同的锁上同步。如果这些方法可以修改来自不同线程的相同值,那么这些方法需要在同一个锁上进行同步,以保证线程安全。这是一个问题的唯一情况是当头==最后(即它们是通过不同成员变量引用的相同对象)。这就是代码在头部和尾部同步的原因 - 大多数情况下,这些都是快速,不受约束的锁,但偶尔头和尾会是同一个实例,其中一个线程将不得不阻止另一个。

只有当队列几乎为空时,剩下的时间put()和poll()才会在队列的不同端工作并且不会相互干扰。

+0

感谢您的回答。有趣的是,围绕头部和尾部同步的主要问题是真正的可见性。看到如何通过替代方法在并发包的LinkedBlockingQueue中实现可见性是非常有趣的。这是真正吸引我的东西。 – 2010-03-12 20:01:01