1

我正在使用双重检查锁定 (DCL) 来避免在不需要时对对象进行同步。在我的情况下,我需要在某个缓冲区为空时进行同步,以让“处理线程”等待“传递线程”再次通知它 - 否则,“处理线程”将在循环中运行而不做任何有用的事情。

两个线程共享这些对象:

Object bufferLock = new Object();
Queue<Command> commands = new ConcurrentLinkedQueue<>(); // thread safe!

线程 1(“交付线程” - 填充缓冲区):

while (true)
    Command command = readCommand();
    commands.add(command);
    synchronize (bufferLock){
        bufferLock.notify(); // wake up Thread 2 (if waiting)
    }
}

线程 2(“处理线程” - 清空缓冲区):

while (true){
    if (commands.peek() == null){ // not creating anything here
        synchronized (bufferLock){
            if (commands.peek() == null){ // also not creating anything
                bufferLock.wait();
            }
        }
    }
    Command command = commands.poll();
    processCommand(command);
}

现在,NetBeans 出现了关于 DCL 的警告,这让我更深入地研究了这个主题,因为我不知道 DCL 的概念——我才开始自己使用它。

据我在网上阅读几篇文章了解到,使用这种模式时有一个Java错误,但所有示例都将它与延迟加载结合使用。在这些示例中,对象是在同步块中创建的。在我的同步代码中,我不创建对象。

我的代码不安全吗?NetBeans 在显示警告方面是否正确?请注意,NetBeans 之前有一个与 DCL 相关的错误,所以我有点困惑。

4

2 回答 2

1

您创建的模式(或者更确切地说,反模式!)并不严格构成双重检查锁定,这通常是指对象引用从 null 开始然后由需要引用它的第一个线程实例化的情况,使用仅在空检查后同步。在 Java 5 之前,严格来说,您无法在 Java 中正确地实现这一点(尽管由于大多数 JVM 的实现方式,您可能会意外地侥幸逃脱)。从 Java 5 开始,您可以使用它,但它基本上没有意义。(您可能对我不久前写的关于 Java 中的双重检查锁定的文章感兴趣,该文章更详细地研究了这个问题。类加载器有效地内置了同步,用于在极少数情况下您确实需要类似的东西DCL。)

现在,这一切都是这样的。严格来说,您在这里所拥有的并不是真正的 DCL。

您确实遇到的问题是您正在尝试混合范式。Java 并发库的存在理由通常是为了避免使用同步和等待/通知的低级锁定。所以你真正应该做的只是使用一些 BlockingQueue 的风格并使用它内置的阻塞行为。同样,在其他示例中,我可能会向您推荐一些我写的关于阻塞队列的材料

于 2013-07-31T23:47:21.417 回答
0

请查看 Brian Goetz 的“Java Concurrency in Practice”一书,第 16.2.4 节“双重检查锁定”。它解释了为什么 DCL 是错误的做法。

于 2013-07-31T21:58:26.963 回答