0

我通过暂停做同样事情的线程等待其中一个完成来改进我的并发程序。但是,它不能正确唤醒线程。这是代码。

//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() 不工作?

4

2 回答 2

1

由于您在检查地图的时间和在地图上操作的时间之间存在不同步的间隙,因此您的逻辑中有许多漏洞,线程可能会错误地进行。您要么需要在地图检查之外进行同步,要么对 ConcurrentMaps 使用一些特殊的原子方法。

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

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

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

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

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

于 2012-07-27T03:46:12.393 回答
0

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

尝试让自己了解 Condition 的基础知识以及为什么它可以将这些竞争条件作为正常信号解决(例如在 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 变量如何解决之前说明的竞态条件的主要基本思想。首先让自己理解这个想法,然后用更合理的东西重新设计你的代码。

于 2012-07-27T07:17:37.710 回答