简体   繁体   中英

why does my notify method not work properly?

I am improving my concurrent program by pausing threads that doing the same thing to wait for one of them finished. However, it cannot not wake up threads properly. Here is the code.

//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;
}

This method is called by many many threads. Before a thread starts calculation, it looks up the entry to see if there is another thread calculating an identical graph. If so, it waits. If not, it continues to calculate. After it figures out the result, it notifies all the threads that is waiting on it.

I use a map to pair up graph and an object. The object is the lock. Please notice that the this map can recognize two identical graphs, that is, the following code returns true.

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

Thus, the entry.get(g) should be ok to be the lock/monitor. However, most of the threads have not been awaken, only 3-4 threads has. When the number of threads that is waiting equals to the number of threads that my computer can create, which means all the threads are waiting, this program would never terminate.

Why exactly does not the entry.get(g).notifyAll() work?

Due to the fact that you have un-synchronized gaps between times that you check the map and times that you operate on the map, you have many holes in your logic where threads can proceed incorrectly. you either need to synchronize outside of your map checks or use some of the special atomic methods for ConcurrentMaps.

when writing concurrent code, i like to pretend there is a malicious gnome running around in the background changing things wherever possible (eg outside of synchronized blocks). here's the first example to get you started:

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

you call entry.get() twice outside of a synchronization block. therefore, the value you get could be different between those 2 calls (the evil gnome changes the map as often as possible). in fact, it could be null when you try to synchronize on it, which will throw an exception.

additionally, wait() calls should always be made in a loop while waiting for a loop condition to change (due to the possibility of spurious wakeups, or, also in your case, multiple wakeups). lastly, you should change the loop condition before you notify. @AdrianShum gave a pretty good overview of how to use wait/notify correctly. your while loop should not be around everything, but within the synchronized block, around the wait call alone. this is not to deal with InterruptedException (a separate issue), but to deal with spurious wakeups and notifyAll calls. when you call notifyAll all waiting threads wake up, but only one can proceed, so the rest need to go back to waiting (hence the while loop).

in short, writing concurrent code is hard , and what you are attempting to implement is not simple. i would recommend reading a good book first (like Josh Bloch's "Java Concurrency In Practice") before attempting to finish this code.

In fact @jtahlborn has already raised the key of problem. I am trying to supplement by telling what's the most obvious issue here.

Try to get yourself understand the basics of Condition and why it can solve those race condition as normal signalling (in windows for example)

Your logic is something like this now (assume obj is referring to same object):

Thread 1:

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

Thread 2:

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

You gotta know thread 1 and thread 2 is run in parallel, therefore you may have something like

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)

Thread 1 is going to wait forever.

What you should do is

Thread 1:

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

Thread 2:

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

That's one of the biggest hole that @jtahlborn is talking I believe (and there are other). Note that setting condition and checking condition are all protected in synchronized block. That's the main basic idea of how Condition variable is solving the race condition illustrated before. Get yourself understand the idea first, and redesign your piece of code with something more reasonable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM