The goal of thread communication is to enable threads to send signals to each other. On the other hand, thread communication enables threads to wait for signals from other threads.
Communication via shared objects
An easy way to send signals between threads is to set the signal value in the variables of the shared object. Thread A sets the boolean member variable hasDataToProcess to true in a synchronization block, and thread B also reads the hasDataToProcess member variable in the synchronization block. This simple example uses an object holding a signal and provides the set and check methods:
public class MySignal{ protected boolean hasDataToProcess = false; public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess = hasData; }}Threads A and B must obtain a reference to a MySignal shared instance for communication. If the references they hold point to different MySingal instances, each other will not be able to detect each other's signals. The data to be processed can be stored in a shared cache area, which is stored separately from the MySignal instance.
Busy Wait
Thread B that is preparing to process the data is waiting for the data to become available. In other words, it is waiting for a signal from thread A, which causes hasDataToProcess() to return true. Thread B runs in a loop to wait for this signal:
protected MySignal sharedSignal = ......while(!sharedSignal.hasDataToProcess()){ //do nothing... busy waiting}wait(), notify() and notifyAll()
Busy waiting does not effectively utilize the CPU running the waiting thread unless the average waiting time is very short. Otherwise, it is wiser to make the waiting thread sleep or non-running until it receives the signal it is waiting for.
Java has a built-in waiting mechanism to allow threads to become non-running while waiting for signals. The java.lang.Object class defines three methods, wait(), notify() and notifyAll(), to implement this waiting mechanism.
Once a thread calls the wait() method of any object, it becomes a non-running state until another thread calls the notify() method of the same object. In order to call wait() or notify(), the thread must first obtain the lock of that object. That is, the thread must call wait() or notify() in the synchronization block. The following is a modified version of MySingal - MyWaitNotify using wait() and notify():
public class MonitorObject{}public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); public void doWait(){ synchronized(myMonitorObject){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } } public void doNotify(){ synchronized(myMonitorObject){ myMonitorObject.notify(); } }}The waiting thread will call doWait(), while the wake-up thread will call doNotify(). When a thread calls the notify() method of an object, one of the threads waiting for the object will be awakened and allowed to execute (Note: this thread to be awakened is random, and it cannot be specified which thread to wake up). Also provided is a notifyAll() method to wake up all threads waiting for a given object.
As you can see, whether it is the waiting thread or the wake-up thread, it calls wait() and notify() in the synchronization block. This is mandatory! If a thread does not hold the object lock, it cannot call wait(), notify() or notifyAll(). Otherwise, an IllegalMonitorStateException exception will be thrown.
(Note: This is how JVM is implemented. When you call wait, it first checks whether the current thread is the owner of the lock, and throws IllegalMonitorStateExcept.)
But how is this possible? When waiting for the thread to execute in the synchronization block, isn't it always holding the lock of the monitor object (myMonitor object)? Can the waiting thread block the wake-up thread entering the synchronous block of doNotify()? The answer is: It's true. Once the thread calls the wait() method, it releases the lock on the held monitor object. This will allow other threads to call wait() or notify() as well.
Once a thread is woken up, the wait() method call cannot be exited immediately until notify() is called.
public class MyWaitNotify2{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignaled = false; public void doWait(){ synchronized(myMonitorObject){ if(!wasSignaled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignaled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignaled = true; myMonitorObject.notify(); } }}
The thread exits its own synchronization block. In other words, the waken thread must regain the lock of the monitor object before it can exit the wait() method call, because the wait method call runs in the synchronization block. If multiple threads are awakened by notifyAll(), then at the same time only one thread can exit the wait() method, because each thread must obtain the lock of the monitor object before exiting wait().
Missed Signals
The notify() and notifyAll() methods do not save the method that calls them, because when these two methods are called, it is possible that no thread is in the waiting state. The notification signal was discarded. Therefore, if a thread calls notify() before being notified before calling wait(), the waiting thread will miss this signal. This may or may not be a problem. However, in some cases, this may make the waiting thread always wait and no longer wake up because the thread misses the wake-up signal.
To avoid losing signals, they must be saved in the signal class. In the MyWaitNotify example, the notification signal should be stored in a member variable of the MyWaitNotify instance. Here is a modified version of MyWaitNotify:
public class MyWaitNotify2{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignaled = false; public void doWait(){ synchronized(myMonitorObject){ if(!wasSignaled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignaled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignaled = true; myMonitorObject.notify(); } }}Note that the doNotify() method sets the wasSignalled variable to true before calling notify(). At the same time, note that the doWait() method checks the wasSignaled variable before calling wait(). In fact, if no signal is received during the time period between the previous doWait() call and this doWait() call, it will only call wait().
(Proof Note: To avoid signal loss, use a variable to save whether it has been notified. Before notify, set yourself to have been notified. After waiting, set yourself to have not been notified and need to wait for notification.)
Fake wake up
For some reason, it is possible that the thread wakes up without calling notify() and notifyAll(). This is called spurious wakeups. Wake up for no reason.
If a false wake-up occurs in the doWait() method of MyWaitNotify2, the waiting thread can perform subsequent operations even if it does not receive the correct signal. This can cause serious problems with your application.
To prevent false wakeup, the member variables that hold the signal will be checked in a while loop, rather than in the if expression. Such a while loop is called spin lock (Note: This approach should be cautious. The current JVM implementation spin consumes CPU. If the doNotify method is not called for a long time, the doWait method will spin continuously, and the CPU will consume too much). The awakened thread will spin until the condition in the spin lock (while loop) becomes false. The following modified version of MyWaitNotify2 shows this:
public class MyWaitNotify3{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignaled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignaled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignaled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignaled = true; myMonitorObject.notify(); } }}Note that the wait() method is in the while loop, not in the if expression. If the waiting thread wakes up without receiving the signal, the wasSignaled variable will become false, and the while loop will be executed again, prompting the wake-up thread to return to the waiting state.
Multiple threads are waiting for the same signal
If you have multiple threads waiting and are awakened by notifyAll(), but only one is allowed to continue execution, using a while loop is also a good way. Only one thread can get the monitor object lock each time, meaning that only one thread can exit the wait() call and clear the wasSignaled flag (set to false). Once this thread exits the doWait() synchronization block, other threads exit the wait() call and check the wasSignaled variable value in the while loop. However, this flag has been cleared by the first awakened thread, so the rest of the awakened threads will return to the waiting state until the next time the signal arrives.
Do not call wait() in string constants or global objects
(Proof note: The string constant mentioned in this chapter refers to variables with constant values)
An earlier version of this article uses string constants ("") as a pipe object in the MyWaitNotify example. Here is the example:
public class MyWaitNotify{ String myMonitorObject = ""; boolean wasSignaled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignaled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignaled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignaled = true; myMonitorObject.notify(); } }}The problem caused by calling wait() and notify() in the synchronization block of an empty string as a lock (or other constant string) is that the JVM/compiler will convert the constant string into the same object. This means that even if you have 2 different MyWaitNotify instances, they all refer to the same empty string instance. It also means that there is a risk that the thread calling doWait() on the first MyWaitNotify instance will be awakened by the thread calling doNotify() on the second MyWaitNotify instance. This situation can be drawn as follows:
At first this may not be a big problem. After all, if doNotify() is called on the second MyWaitNotify instance, what really happens is that threads A and B are wrongly awakened. The wake-up thread (A or B) will check the signal value in the while loop and then return to the waiting state, because doNotify() is not called on the first MyWaitNotify instance, and this is the instance it is waiting for. This situation is equivalent to triggering a false awakening. Thread A or B wakes up without the signal value being updated. But the code handles this situation, so the thread returns to the waiting state. Remember, even if 4 threads call wait() and notify() on the same shared string instance, the signals in doWait() and doNotify() will be saved by 2 MyWaitNotify instances respectively. A doNotify() call on MyWaitNotify1 may wake up the thread of MyWaitNotify2, but the signal value will only be saved in MyWaitNotify1.
The problem is that since doNotify() only calls notify() instead of notifyAll(), even if there are 4 threads waiting on the same string (empty string) instance, only one thread is awakened. So, if thread A or B is awakened by a signal sent to C or D, it will check its own signal value to see if any signal is received and then return to the waiting state. Neither C nor D were awakened to check the signal value they actually received, so the signal was lost. This situation is equivalent to the problem of missing signals mentioned above. C and D have been sent to the signal, but neither can respond to the signal.
If the doNotify() method calls notifyAll() instead of notify(), all waiting threads will be awakened and the signal value will be checked in turn. Threads A and B will return to the waiting state, but only one thread in C or D notices the signal and exits the doWait() method call. The other in C or D will return to the waiting state because the thread that obtained the signal clears the signal value (set to false) during the process of exiting doWait().
After reading the above paragraph, you may try to use notifyAll() instead of notify(), but this is a performance-based bad idea. When only one thread can respond to the signal, there is no reason to wake up all threads every time.
So: in the wait()/notify() mechanism, do not use global objects, string constants, etc. The corresponding unique object should be used. For example, each instance of MyWaitNotify3 has its own monitor object instead of calling wait()/notify() on an empty string.
The above is the information on Java multi-threading and thread communication. We will continue to add relevant information in the future. Thank you for your support for this site!