Wait, notify, and notifyAll are briefly introduced in locks and monitors
All objects have a lock and monitor associated with them
Wait, notify, and notifyAll are Object methods because any Object can be used as a lock Object (lock Object is also a critical resource)
Wait and wake up are themselves critical resources
- Wait. Wait for what? Waiting to acquire critical resources
- Wake up? Wake up what? Wakes up the thread waiting for a critical resource
So, wait or wake up, are inseparable from the critical resource, and the Object as a lock, is the critical resource
And this is why must be used in synchronous method (synchronized code block) wait and notify and notifyAll, because they have to hold a critical resource (lock) monitor, only hold the specified lock monitor, to be able to related operations, and which lock must be held, and can only be operated on the lock (critical resources)
This is also easy to accept and understand because thread communication in Java is for the monitor (lock, critical resource), wait and wake up on the monitor
You don’t even have a monitor. What are you doing? The a-monitor you have, what are you doing on b-monitor?
Multithreaded Collaboration Wait, Notify, and notifyAll
Thread communication
Wait and notify examples
In the following code example, the MessageQueue class, which has an internal LinkedList, can be used to hold messages called Message
The default internal number of MessageQueue is 10, which can be set manually using the constructor
The production method SET and the get method get are provided
If the queue is full, wait, otherwise produce the message, and notify the consumer to get the message
If the queue is empty, wait, otherwise consume the message, and notify the producer to produce the message
Open up two threads in the test class, one for production and one for consumption (execute in an infinite loop)
package test1; import java.util.LinkedList; Public class T13 {public static void main(String[] args) {final MessageQueue = new MessageQueue(3); System.out.println("***************task begin***************"); // Create producer Thread and start new Thread(() -> {while (true) { mq.set(new Message()); }},"producer").start(); // Create consumer Thread and start new Thread(() -> {while (true) { mq.get(); }},"consumer").start(); }} /** * MessageQueue {/** * private final int Max; /* * lock * */ private final byte[] Lock = new byte[1]; /** * final LinkedList<Message> messageQueue = new LinkedList<>(); /** * The constructor default queue size is 10 */ publicMessageQueue() { max = 10; } public MessageQueue(int x) {Max = x; } public voidset(Message Message) {synchronized (lock) {// If the number of synchronized queues exceeds that of synchronized (lock) {// If the number of synchronized queues exceeds that of synchronized (lock)if (messageQueue.size() > max) {
try {
System.out.println(Thread.currentThread().getName() + " : queue is full ,waiting..."); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); Println (thread.currentThread ().getName() +)" : add a message");
messageQueue.addLast(message);
lock.notify();
}
}
public void get() {synchronized (lock) {// if the queue is empty, the message cannot be obtainedif (messageQueue.isEmpty()) {
try {
System.out.println(Thread.currentThread().getName() + " : queue is empty ,waiting..."); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); Println (thread.currentThread ().getName() +);} // If the queue is not empty, the message is read and the waiting Thread on the lock is notified" : get a message"); messageQueue.removeFirst(); lock.notify(); } /** * The Message stored in the Message queue */ class Message {}Copy the code
If (messagequeue.size () > Max), the actual queue space is 4
As you can see from the above code examples, communication and mutual exclusion between producers and consumers are implemented with the help of a lock lock
They are managed based on this critical resource. This lock acts as the center of the schedule. After entering the monitor, if the condition is met, it is executed, and other threads are notified, and if not, it is waited.
The relationship between the lock and monitor and thread communication should be understood from this example
Wait method
There are three versions of the wait method, wait, which means that the lock is waiting for the monitor that holds the lock object.
For both no-argument wait and two-argument wait, you can look at the source code, which has this native method at its core
Wait () calls wait (0) directly;
Wait (long timeout, int nanos)
Take a closer look at native methods
API:
Call notify() or notifyAll() on this object in another thread.
Or before the specified amount of time is exceeded.
Causes the current thread to wait.
Or before the specified amount of time is exceeded.
Causes the current thread to wait.
As mentioned earlier, wait, as well as notify and notifyAll, need to hold a monitor to call this method
Since the other two versions rely on the underlying WAIT, all versions of the WAIT need to hold the monitor
Once called, the method enters the monitor’s wait set and the synchronization requirement is abandoned (that is, the lock is no longer held and will be released)
Be sure to note:
Locks will be released, locks will be released, locks will be released……
Locks will be released, locks will be released, locks will be released……
Unless one of these conditions is encountered, the thread is disabled and goes to sleep, that is, to wait
In these cases, threads are removed from the object’s wait set and rescheduled
Note that deleting from the wait set does not mean executing immediately, it still has to compete with other threads and continue to wait if the race fails
If a thread is in more than one wait set, it will only unlock the current one, and it will remain locked in other wait sets. If you are in multiple wait sets, you can’t just free all of the wait sets at once, right
If any other thread interrupts the thread while it is waiting, you receive an exception, InterruptedException
If there is no other holds the current monitor, will throw an exception IllegalMonitorStateException
Summary:
For the native method wait, the specified length of time will be waited, and if wait (0), the wait will continue
To wait () without arguments is to wait continuously
The two-parameter version is waiting for a certain amount of time
False wake up of wait
Threads can also be woken up without being notified, interrupted, or timed out. This is called spurious wakeup.
This means you don’t wake him up (notifications, interruptions, timeouts), it’s totally unexpected, and you wake up somehow
Although the probability of this happening is very small, it should be taken care of
How to prevent?
Like our producer method above
public void set(Message Message) {synchronized (lock) {// If the number of synchronized queues exceeds that of synchronized (lock) {// If the number of synchronized queues exceeds that of synchronized (lock)if (messageQueue.size() > max) {
try {
System.out.println(Thread.currentThread().getName() + " : queue is full ,waiting..."); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); Println (thread.currentThread ().getName() +)" : add a message"); messageQueue.addLast(message); lock.notify(); }}Copy the code
In the producer method, we use if to judge the condition
if (messageQueue.size() > max)
In the event of a false wake up, execution continues after the wait method, which follows
messageQueue.addLast(message);
lock.notify();
Obviously, in the case of false awakening, the condition is likely to remain unsatisfied, and if you continue to produce, isn’t it wrong?
So we should make conditional judgments again after awakening. How?
You can change the if condition judgment into the while condition test, so that it will confirm again whether the condition is met even after the wake up. If not, it will definitely continue to wait, but will not continue to execute
Summary:
We should always use a loop test condition to ensure that the condition is actually met and to avoid the rare occurrence of false wake up problems
Notify method
Notify is also a local method that wakes up a thread waiting on the monitor (keywords: current monitor, a thread)
Even if there are multiple threads waiting on the monitor, only one is awakened
And the choice is arbitrary
In addition, we need to pay attention to this side after notify, so immediately there is any reaction? No!
Only when the thread currently holding the monitor completes execution does the awakened thread have a chance to execute, and the awakened thread still has to compete (if there are threads waiting in the entry set).
So, if you take a method of 1000 lines, and you execute notify on any line, you’re going to have to wake up the thread after the method ends, right
Notify the problem
Notify only wakes up one of the threads, and this mechanism is unfair, meaning that there is no guarantee that every thread will have a chance to execute.
To put it another way, for example, 10 children are waiting for the teacher to hand out candy. If they choose one at random each time, some children may not get candy all the time
This leads to thread starvation
How to solve it?
We also have notifyAll, which has the same function as notify, but the difference is that it wakes up all waiting threads, so that all waiting sets have a chance to regenerate. Of course, if the conditions are not met, they may continue to enter the waiting set, and if the competition is not successful, they also wait in the entry set
NotifyAll ensures that no one gets hungry
NotifyAll method
This is also a native method, and as you can see, you still end up using the UNDERLYING JVM, whether it’s waiting or notification. Through the operating system to achieve
NotifyAll wakes up all threads waiting on the monitor of this object
The number of threads to wake up is the same as notify. The number of threads to wake up is the same as notify
Multithreaded communication
Wait and notify can be used to communicate between threads, and wait and notifyAll can be used to communicate between threads
In fact, for our uppermost code example, it is not only false awakenings that cause problems, but also non-false awakenings
This is not a problem when there is only one producer and consumer, but it can be a problem in more multi-threaded scenarios
For example, two producers, A, and B, and one consumer, C, execute for A period of time, and let’s say the queue is full
If A is running and is found to be full, wait
Then thread B executes, still full, and enters the wait
Thread C then starts executing, consuming A message and calling notify, which happens to wake up thread A
After thread C executes, thread A competes and enters the synchronization zone to execute. Thread A produces A message and then calls notify
Unfortunately, thread B woke up, and thread B woke up, and it won, and it continued, and it continued to add to the queue, so it called addLast
Obviously, something is wrong, something is full but the addLast method is still called
In this scenario, the problem occurs when a thread is woken up and the condition is not satisfied. For example, in the description above, the consumer should be woken up, but the producer is woken up and the condition is not satisfied
Similarly, if the queue is empty, let’s say we have two consumer threads A, B, and A producer C
Consumer A, found empty, wait
Consumer B, found empty, wait
Producer C produces A message, notify, wake up A
After A wakes up, the competition succeeds. After consuming A message, notify wakes up B
When B wakes up and the race is successful, it will continue consuming the message, appear empty, but still call the removeFirst method
The results are similar to false awakenings – when you wake up, the condition is still not met
So the solution is to change the if condition detection to the while condition detection
It can also be seen from this that we should always use while to detect conditions, not only to avoid false awakenings, but also to avoid synchronization problems with more concurrent threads
If we use while for condition checking
Let’s say we have 10 producers, queue size 5, one consumer
It just so happens that 10 producers start running, then the queue is full and all 10 threads are in wait state
What happens next is that the consumer keeps consuming, keeps consuming five messages, wakes up five of the producers, and then enters wait
What happens if the next five producers wake up the same producers that just entered the wait?
Eventually all producers will enter the wait state! And that customer is still Wait! Everyone is waiting. Who will unlock it?
One of the problems is that we don’t know which thread notify will wake up, and there are scenarios where consumers never get a chance to execute
A notifyAll should be used to ensure that the consumer will always have a chance to execute, even if he or she does not have a chance to execute, and that if he or she is awake he or she will have a chance to animate the workshop
As shown in the figure below, the original MessageQueue is reconstructed into RefactorMessageQueue, actually only changing if to while
In the test method, the queue is set to 5 (> judge is used in the code, so it is actually 6), the producer is set to 20, you can see that it soon deadlocks, and the thread is given a name
***************task begin***************
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : queue is full ,waiting…
producer1 : queue is full ,waiting…
producer2 : queue is full ,waiting…
producer3 : queue is full ,waiting…
producer4 : queue is full ,waiting…
producer5 : queue is full ,waiting…
producer6 : queue is full ,waiting…
producer7 : queue is full ,waiting…
producer8 : queue is full ,waiting…
producer9 : queue is full ,waiting…
producer10 : queue is full ,waiting…
producer11 : queue is full ,waiting…
producer12 : queue is full ,waiting…
producer13 : queue is full ,waiting…
producer14 : queue is full ,waiting…
producer15 : queue is full ,waiting…
producer16 : queue is full ,waiting…
producer17 : queue is full ,waiting…
producer18 : queue is full ,waiting…
producer19 : queue is full ,waiting…
consumer : get a message
consumer : get a message
consumer : get a message
consumer : get a message
consumer : get a message
consumer : get a message
consumer : queue is empty ,waiting…
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : add a message
producer0 : queue is full ,waiting…
producer6 : queue is full ,waiting…
producer11 : queue is full ,waiting…
producer10 : queue is full ,waiting…
producer9 : queue is full ,waiting…
producer8 : queue is full ,waiting…
producer7 : queue is full ,waiting…
producer5 : queue is full ,waiting…
producer4 : queue is full ,waiting…
producer3 : queue is full ,waiting…
producer2 : queue is full ,waiting…
producer1 : queue is full ,waiting…
The key part, as shown in the figure below, is that consumers wait, followed by producers who are full, and then wait
You can view it using the Jconsole tool
This is the official tool provided by the local installation and configuration of JDK, you can directly enter the command line: jConsole, and then will open an interface window
- Enter jConsole on the command line
- Select the process and connect
- Click thread to view
Looking at the state of each thread one by one, you’ll see that our 20 producers producerX (0-19) and one consumer are all: state: [WAITING on B@2368a10b
Summary:
In multithreaded scenarios, loop condition detection should always be done using while, and notifyAll should always be used instead of notify to avoid strange threading problems
conclusion
Wait, notify, and notifyAll all require a monitor to operate, and to enter a monitor, that is, a synchronized method or a block of code, or a block of code synchronized with an explicit lock
Wait is an InterruptedException that is thrown when interrupted by another thread. The interrupt status is erased and the thread is awakened
Because of the problems with notify scenarios, we should use notifyAll whenever possible