为什么我的通知方法无法正常工作?

时间:2012-07-27 01:44:32

标签: java locking wait notify

我正在改进我的并发程序,暂停执行相同操作的线程等待其中一个完成。但是,它不能正确唤醒线程。这是代码。

//to store graphs, if a thread finds the graph it is going to compute is in the entry, it waits, otherwise it compute then notify all other threads waiting on it.
Map<Graph, Object> entry = new ConcurrentHashMap<Graph, Object>();

public Result recursiveMethod(Graph g) {
        if (entry.get(g) != null) {//if the graph is in the entry, waits
            synchronized(entry.get(g)) {
                entry.get(g).wait();
            }
            //wakes up, and directly return the result
            return result;
        }
        synchronized(entry) {
            if (entry.get(g) == null)//if the graph is not in the entry, continue to compute
            entry.put(g,new Object());
        }
        //compute the graph recursively calls this method itself...
        calculate here...
        //wake up threads waiting on it, and remove the graph from entry
        synchronized(entry.get(g)){
            entry.get(g).notifyAll();
        }
        entry.remove(g);
        return result;
}

许多线程都会调用此方法。在线程开始计算之前,它会查找条目以查看是否有另一个线程计算相同的图形。如果是这样,它等待。 如果没有,它继续计算。在确定结果后,它会通知正在等待它的所有线程。

我使用地图配对图形和对象。对象是锁。 请注意,此映射可以识别两个相同的图形,即以下代码返回true。

Graph g = new Graph();
entry.put(g, new Object());
Graph copy = new Graph(g);
entry.get(g) == entry.get(copy) //this is true

因此,entry.get(g)应该可以成为锁/监视器。 但是,大多数线程都没有被唤醒,只有3-4个线程。 当等待的线程数等于我的计算机可以创建的线程数(这意味着所有线程都在等待)时,该程序将永远不会终止。

为什么entry.get(g).notifyAll()无法正常工作?

2 个答案:

答案 0 :(得分:1)

由于您在检查地图的时间与在地图上操作的时间之间存在未同步的间隙,因此逻辑中的许多孔会导致线程无法正确进行。您需要在地图检查之外进行同步,或者对ConcurrentMaps使用一些特殊的原子方法。

在编写并发代码时,我想假装有一个恶意gnome在后台运行,尽可能地改变事物(例如在同步块之外)。这是第一个让你入门的例子:

    if (entry.get(g) != null) {//if the graph is in the entry, waits
        synchronized(entry.get(g)) {

在同步块之外调用entry.get()两次。因此,你得到的值可能是那两个调用之间的不同(邪恶的gnome会尽可能频繁地更改地图)。事实上,当你尝试对它进行同步时,它可能为null,这将引发异常。

另外,在等待循环条件改变时,应始终在循环中进行wait()调用(由于可能出现虚假唤醒,或者在您的情况下,还有多次唤醒)。最后,您应该在通知之前更改循环条件。 @AdrianShum非常好地概述了如何正确使用wait / notify。你的while循环不应该在所有内容周围,而是在synchronized块内,仅在wait调用周围。这不是处理InterruptedException(一个单独的问题),而是处理虚假的唤醒和notifyAll调用。当你调用notifyAll 所有等待线程唤醒时,但只有一个可以继续,所以其余的需要回到等待(因此是while循环)。

简而言之,编写并发代码是 hard ,而您尝试实现的并不简单。在尝试完成此代码之前,我建议先阅读一本好书(如Josh Bloch的“Java Concurrency In Practice”)。

答案 1 :(得分:0)

事实上@jtahlborn已经提出了问题的关键。我想通过告诉这里最明显的问题来补充。

尝试让自己了解条件的基础知识以及它为什么能够将这些竞争条件作为正常信号解决(例如在windows中)

你的逻辑现在是这样的(假设obj指的是同一个对象):

主题1:

if (!hasResult) {
    synchronized(obj) {
        obj.wait();
    }
}

主题2:

hasResult = false;
// do your work
synchronized(obj) {
   obj.notify();
}
hasResult= true;

你必须知道线程1和线程2并行运行,因此你可能有类似

的东西
Thread 1                   Thread 2
                         hasResult = false
if (!hasResult)
                         do your work
                         synchronized(obj)
                         obj.notify()
                         end synchronized(obj)

synchronized(obj)
obj.wait()
end synchronized(obj)

线程1将永远等待。

你应该做的是

主题1:

synchronized(obj) {
    while (hasResult) {
        obj.wait();
    }
}

主题2:

hasResult = false;
synchronized(obj) {
   // do your work
   obj.notify();
   hasResult=true;
}

这是@jtahlborn说话的最大漏洞之一我相信(还有其他的)。请注意,设置条件和检查条件都在同步块中受到保护。这是Condition变量如何解决之前说明的竞争条件的主要基本思想。首先让自己理解这个想法,然后用更合理的东西重新设计你的代码。