2012-07-27 163 views
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()工作?

+1

我无法理解代码,也许是因为您没有包含所有代码。您是否注意到“if(entry.get(g)!= null)”将始终返回true,因为您刚插入条目。因此,你会一直等待。 – JimN 2012-07-27 02:00:51

+0

除非我提供的代码没有问题,否则不需要查看所有其他代码。谢谢你的回答,我发现当我在这里重写它时,我只是把if语句写错了,我改变了它。问题依然存在。 – user1556378 2012-07-27 02:48:00

+0

@ user1556378问题是你的代码充满了问题,没有任何意义。线程是如何产生的?什么是“结果”?你想达到什么目的?我已经读了3次你的代码,但仍然无法理解它。 – 2012-07-27 04:35:28

回答

1

由于您检查地图的次数与您在地图上操作的次数之间存在未同步的间隔,因此您的逻辑中可能存在许多线程无法正常处理的洞。您需要在地图检查之外进行同步,或者对ConcurrentMaps使用一些特殊的原子方法。当我编写并发代码时,我喜欢假装有一个恶意的gnome在后台运行,在任何可能的地方(例如,在同步块之外)改变东西。这里的第一个例子,让你开始:

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

你叫entry.get()两次同步块之外。因此,你得到的价值可能是这两个调用之间的不同(邪恶的侏儒尽可能经常改变地图)。事实上,当你尝试同步它时它可能是空的,这会抛出一个异常。

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

简而言之,编写并发代码是,你试图实现的并不简单。我会建议先阅读一本好书(如Josh Bloch的“Java并发实践”),然后再尝试完成此代码。

+0

还有一个问题,entry.put和entry.remove没有正确同步。 OP应该看看ConcurrentHashMap的API文档,关于地图上的同步不会使其他方法具有序列化访问。 put逻辑应该用ConcurrentMap – 2012-07-27 04:37:43

+0

中的相应方法替换对,我并不熟悉并发。感谢所有这些建议。我将去阅读有关并发HashMap的内容。对于wait()方法,我确实有一个while循环将其包装在我的实际代码中,但这只是为了在被中断的情况下,循环本身不是必需的,问题不在循环中。这就是为什么我没有在这里包括它。对于未同步的差距,我是否应该同步其他计算部分或仅处理concurrentMap的行? – user1556378 2012-07-27 06:35:25

0

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

试图让自己了解情况的基础知识,以及为什么它可以解决这些竞争状态为正常的信号(在例如窗户)

你的逻辑是这样的,现在(假定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变量如何解决之前说明的竞争条件的主要基本思想。先让自己理解这个想法,然后用一些更合理的方式重新设计你的代码。

相关问题