Hashtag: “We are all little frogs” public account article

If a thread executes from beginning to end without dealing with other threads, there are no security issues. However, collaboration is increasingly becoming the trend of social development. After a large task is divided into several small tasks, each small task may also need to cooperate with each other to finally execute a complete large task. Therefore, each thread can communicate with each other during execution. The so-called communication means exchanging some data or sending some control instructions to each other. For example, one thread sends an instruction to resume execution to another thread that has suspended execution.

Volatile and synchronized

Mutable shared variables are a natural communication medium, that is, if one thread wants to communicate with another thread, it can modify a variable shared between multiple threads, and the other thread can read the shared variable to obtain the content of the communication.

Because of atomic operations, memory visibility, and instruction reordering, Java provides volatile and synchronized synchronization to ensure that communication is correct. Without these synchronization methods, one thread’s writing cannot be immediately observed by another thread, and communication is unreliable

wait/notifymechanism

The story background

Also do not know is that day killed to our school toilet pit filled a plastic bottle, resulting in the corridor such as the Yellow River flooding general, smelly sky. What is more tragic is that there is only such a toilet in the whole building. What is more tragic than this is that there is only a pit in the toilet !!!!! Ok, let’s describe the toilet in Java:

public class Washroom {

    private volatile boolean isAvailable = false;    // Indicates whether the toilet is available

    private Object lock = new Object(); // Lock the toilet door

    public boolean isAvailable(a) {
        return isAvailable;
    }

    public void setAvailable(boolean available) {
        this.isAvailable = available;
    }

    public Object getLock(a) {
        returnlock; }}Copy the code

The isAvailable field indicates whether the toilet isAvailable, which defaults to false due to toilet damage, and the lock field indicates the lock on the toilet door. Note that the isAvailable field is volatile, which means that if one thread changes its value, it is immediately visible to other threads

As toilet resources are precious, wise school leaders immediately set out a restoration mission:

public class RepairTask implements Runnable {

    private Washroom washroom;

    public RepairTask(Washroom washroom) {
        this.washroom = washroom;
    }

    @Override
    public void run(a) {

        synchronized (washroom.getLock()) {
            System.out.println("Maintenance workers have obtained the lock on the toilet.");
            System.out.println("Toilet maintenance, toilet maintenance is a hard job, it takes a long time...");

            try {
                Thread.sleep(5000L);    // Use thread sleep to indicate the repair process
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            washroom.setAvailable(true);        // Set the toilet to usable state after maintenance
            System.out.println("Maintenance has fixed the toilet and is ready to release the lock."); }}}Copy the code

The maintenance plan is that when the repairman enters the Washroom, he should first lock the door and start the repair. After the repair, he should set the isAvailable field of Washroom to true to indicate that the Washroom isAvailable.

At the same time, a group of impatient guys are hanging around the toilet door. I don’t have to say what they want to do 😏😏 :

public class ShitTask implements Runnable {

    private Washroom washroom;

    private String name;

    public ShitTask(Washroom washroom, String name) {
        this.washroom = washroom;
        this.name = name;
    }

    @Override
    public void run(a) {
        synchronized (washroom.getLock()) {
            System.out.println(name + "Got the lock on the toilet.");
            while(! washroom.isAvailable()) {/ / has been, etc
            }
            System.out.println(name + "Out of the bathroom."); }}}Copy the code

This ShitTask describes the process of going to the toilet. It first obtains the lock of the toilet and then determines whether the toilet is available. If not, it continuously determines whether the toilet is available in an endless loop until the toilet is available, and then releases the lock and leaves the toilet.

Then let’s see what’s happening in the real world:

public class Test {
    public static void main(String[] args) {
        Washroom washroom = new Washroom();
        new Thread(new RepairTask(washroom), "REPAIR-THREAD").start();

        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(new ShitTask(washroom, "Dog elder brother"), "BROTHER-DOG-THREAD").start();
        new Thread(new ShitTask(washroom, "The cat ye"), "GRANDPA-CAT-THREAD").start();
        new Thread(new ShitTask(washroom, Wang Ni Mei), "WANG-NI-MEI-THREAD").start(); }}Copy the code

The school first let the maintenance workers into the toilet to repair, and then the army of toilet users, including Brother Dog, Uncle Cat and Sister Wang, began to walk around the toilet. Let’s look at the execution result:

In toilet maintenance, repairing toilets is hard work and takes a long time... The maintenance workers have repaired the toilet and are ready to release the lock. Wang Nimei has got the lock of the toilet. Wang Nimei has finished using the toiletCopy the code

It looks like nothing is wrong, but looking back at the code, there are two peculiarities:

  1. After the main THREAD starts the REPAIR THREAD, the sleep method must be called to wait for a period of time before the REPAIR THREAD is allowed to start.

    If repairthread is opened at the same time as any other toilet THREAD, there is a chance that the person who uses the toilet, for example dog, will get the lock and the repairman won’t be able to get into the toilet at all. But it’s probably true that the dog got to the bathroom first, and then the maintenance guy. But instead of staying in the toilet all the time, dog should come out and wait until the repairman says he’s ready to go in. So that’s kind of weird

  2. A toilet user must constantly determine whether the isAvailable field in Washroom is true when he or she obtains the lock.

    If a person enters the toilet and finds that it is still not working, it should rest somewhere, and when the repairman has fixed the toilet, he can call the person who is waiting for the toilet. There is no need to constantly check that the toilet is fixed.

To sum up, after a thread has acquired the lock, if the specified condition is not met, it should take the initiative to relinquish the lock, and then wait in the special waiting area until a thread completes the specified condition, and then notify the thread waiting for the condition to complete, let them continue to execute.

If you think the above sentence is more round, I will translate it for you: When the dog gets the lock of the toilet, if the toilet is not available, he will voluntarily give up the lock, and then queue up to wait for the toilet until the repairman has repaired the toilet. After the toilet status is set to available, the repairman will notify the people who need to go to the toilet, so that they can go to the toilet normally.

Specific use mode

To achieve this, Java proposes a mechanism called Wait /notify. When a thread to get the lock, if the condition is not satisfied, it will take the initiative to relinquish the lock, and then put the thread into a waiting queue to wait, until a thread to complete the condition, notify the thread in the waiting queue they wait for the condition is satisfied, can continue to run!

What if different threads have different wait conditions? You can’t all jam into the same wait queue, right? Yes, in the Java rules for every lock corresponds to a waiting queue, which means if one thread in acquiring the lock after find a condition is not satisfied, will take the initiative to release the lock and then put the thread in and get it to the lock of the waiting queue, when another thread to complete the corresponding conditions need to acquire the same lock, Notify it of the waiting queue for the lock it acquired when the condition is complete. This process means that the lock and the wait queue establish a one-to-one association.

Java has specified how to release the lock and put the thread on the wait queue associated with the lock and how to notify the thread on the wait queue that the condition has been completed. The lock is an Object, and there are several methods defined on the ancestor of all objects:

public final void wait(a) throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException

public final void notify(a);
public final void notifyAll(a);
Copy the code
The method name instructions
wait() After a thread acquires the lock, this method of the lock object is called, the thread releases the lock and places the thread on the wait queue associated with the lock object
wait(long timeout) withwait()The method is similar except that the thread waits for a specified number of milliseconds and is automatically removed from the wait queue if the specified time is exceeded
wait(long timeout, int nanos) Same as above, except that the timeout granularity is smaller, that is, the specified number of milliseconds Ghana seconds
notify() Notifies a thread in the wait queue associated with the lock object to return from wait() and continue execution
notifyAll() Similar to the above, except that all threads in the wait queue are notified

Now that we know what these methods mean, let’s rewrite ShitTask:

public class ShitTask implements Runnable {

    / /... Related fields and constructors have been omitted to save space

    @Override
    public void run(a) {
        synchronized (washroom.getLock()) {
            System.out.println(name + "Got the lock on the toilet.");
            while(! washroom.isAvailable()) {try {
                    washroom.getLock().wait();  // Call the wait() method of the lock object to yield the lock and place the current thread on the wait queue associated with the lock
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(name + "Out of the bathroom."); }}}Copy the code

Look, we added this code to the endless loop of determining whether a toilet is usable:

washroom.getLock().wait(); 
Copy the code

This code is meant to yield the toilet lock and place the current thread in the wait queue associated with the toilet lock.

Then we also need to modify the maintenance task:

public class RepairTask implements Runnable {

    / /... Related fields and constructors have been omitted to save space

    @Override
    public void run(a) {

        synchronized (washroom.getLock()) {
            System.out.println("Maintenance workers have obtained the lock on the toilet.");
            System.out.println("Toilet maintenance, toilet maintenance is a hard job, it takes a long time...");

            try {
                Thread.sleep(5000L);    // Use thread sleep to indicate the repair process
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            washroom.setAvailable(true);    // Set the toilet to usable state after maintenance
            
            washroom.getLock().notifyAll(); // Notify all threads in the wait queue associated with the lock object that they can continue executing
            System.out.println("Maintenance has fixed the toilet and is ready to release the lock."); }}}Copy the code

As you can see, we added this line after the repair:

washroom.getLock().notifyAll();
Copy the code

This code indicates that all threads in the wait queue associated with the lock object are notified that they can continue execution.

After modifying the ShitTask and RepairTask using Java’s Wait /notify mechanism, we restore the entire realistic scenario:

public class Test {
    public static void main(String[] args) {
        Washroom washroom = new Washroom();
        new Thread(new ShitTask(washroom, "Dog elder brother"), "BROTHER-DOG-THREAD").start();
        new Thread(new ShitTask(washroom, "The cat ye"), "GRANDPA-CAT-THREAD").start();
        new Thread(new ShitTask(washroom, Wang Ni Mei), "WANG-NI-MEI-THREAD").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        
        new Thread(new RepairTask(washroom), "REPAIR-THREAD").start(); }}Copy the code

Wait /notify: Wait /notify: Wait /notify: Wait /notify: Wait /notify

The dog got the lock of the toilet. The cat got the lock of the toilet. The maintenance man got the lock of the toilet. The maintenance man has fixed the toilet and is ready to release the lock. Wang Nimei has gone to the toilet. The cat has gone to the toiletCopy the code

As can be seen from the execution result, although Brother Dog, Master Cat and Sister Wang arrived at the toilet first and got the lock, because the toilet was not available, they all called wait() method first to give up the lock, and then hid in the waiting queue associated with the lock until the repairman finished repairing the toilet. After notifying brother Dog, Uncle Cat and Sister Wang in the waiting queue, they began to continue the procedure of going to the toilet

General pattern

Now that you have a general idea of wait/notify, let’s summarize the general pattern of wait/notify. Let’s first look at the general pattern for waiting threads:

  1. Get the object lock.
  2. If a condition is not met, the lock object’swaitMethod, still check if the condition is met after being notified.
  3. If the condition is met, the code continues to execute.

The general code is as follows:

synchronized(object) {processing logic (optional)while{object. Wait (); Processing logic (optional)}Copy the code

Other than the code that determines whether the condition is met and calls the WAIT method, the processing logic is optional.

Let’s look at the general pattern for notification threads:

  1. Get the lock of the object.
  2. Completion conditions.
  3. Notifies the waiting thread in the wait queue.
synchronized(object) {Completion condition object. NotifyAll (); },Copy the code

Tip: Don’t forget that synchronized methods also use locks. Static synchronized methods lock on the Class object, while member synchronized methods lock on the this object. So, if not explicitly, the synchronized code block described below also contains synchronized methods.

Now that you know the general wait/notify mode, you need to be careful with the following aspects:

  • You must call wait, notify, or notifyAll in a synchronized block of code.

    Why do wait/notify methods have to be called in synchronous blocks? Wait (notify) removes a thread from the wait queue. Wait (notify) removes a thread from the wait queue.

    A: Because wait is run in the wait thread, notify or notifyAll is run in the notification thread. To execute a wait, check whether a condition is met or not. This is a check before execution operation, not an atomic operation. Therefore, in a multi-threaded environment without locking, the order of execution of the waiting thread and the notification thread might look like this:

    When the wait thread decides that the wait condition is not met and is about to notify, the notify thread completes the condition first and then executes the wait method. As a result, the wait thread stays in the wait queue forever and no one notifies it. Therefore, the checking condition, calling the wait method, calling the completion condition, and calling the notify method in the waiting thread should all be atomic operations, which are mutually exclusive. Therefore, the same lock is used to synchronize these two atomic operations to avoid the embarrassing situation that the waiting thread will wait forever.

    If wait, notify, or notifyAll is not called in a synchronized block, that is, wait is called without obtaining the lock, like this:

    Object. The wait ();Copy the code

    Is thrown IllegalMonitorStateException abnormal.

  • In a block of synchronized code, the wait, notify, or notifyAll methods of the acquired lock object must be called.

    That is, you cannot call wait, notify, or notifyAll of an object. For example, the code in the waiting thread looks like this:

    synchronized(object1) {
        while(Condition not met) {object2.wait();    // Call the wait method of any object}}Copy the code

    The code in the notification thread looks like this:

    synchronized(object1) {Complete the condition object2.notifyAll();
    }
    Copy the code

    For object code 2. Wait (), said surrendered the object of the current thread holds 2 lock, and the current thread object holding 1 lock, so it is wrong to write so, also sell IllegalMonitorStateException abnormal. This means that if the current thread does not hold the lock on an object, it cannot invoke the wait method on that object to yield the lock. So if you want the waiting thread to relinquish the currently held lock, you can only call the object 1.wait(). The thread is then placed on the wait queue associated with object 1. NotifyAll () can only be called on notification threads to notify the waiting threads.

  • The waiting thread should use while instead of if to determine whether a condition is satisfied.

    In other words, use while to determine whether the condition is satisfied:

    while(Condition not met) {/ / ✅ correctlyObject. The wait (); }Copy the code

    Instead of using if:

    if(Condition not met) {/ / error ❌Object. The wait (); }Copy the code

    This is because under the condition of multithreading, may be in one thread calls notify immediately after another thread changed to not meet the conditions, such as the maintainer to fix the toilet after notice everyone go to the toilet now, there is a small fart child in the ear of pit filled a bottle to the toilet, toilet is made unavailable and state, Those waiting to go to the bathroom still need to judge whether the conditions are met before continuing.

  • After notify or notifyAll is called, the waiting thread does not return from wait() immediately. Instead, it must return from wait() until the notify() or notifyAll() thread releases the lock.

    That is, if the notification thread has code to execute after calling notify or notifyAll on the lock object, it looks like this:

    synchronized(object) {Completion condition object. NotifyAll (); . Post-notification processing logic}Copy the code

    After the notification processing logic is complete, the lock is released so that other threads can recover from the wait state and re-compete for the lock to execute the code. For example in mechanic repaired the toilet after and inform the people waiting to go to the bathroom, he still hasn’t come out from the toilet, but the writing on the wall of toilet visit “XXX” such words just come out from the toilet, after come out from the bathroom to represent the release the lock, the elder brother of the dog, cat ye, waunny sister began to compete for access to the toilet.

  • Notify removes only one thread from the wait queue, while notifyAll removes all threads from the wait queue.

    The notifyAll method is called notify instead of notifyAll

waitandsleepThe difference between

If you are aware that wait and sleep both pause threads and contain InterruptedException, what is the difference?

  • Wait is a member method of Object, while sleep is a static method of Thread.

    Any object that acts as a lock can call its own wait method in a synchronized block of code. Sleep is the static method of a Thread and represents the specified time for the current Thread to sleep.

  • A call to wait requires the lock to be acquired; a call to sleep does not.

    Again, be sure to call the wait method on the lock object in the synchronized block if you want to get the lock! The prerequisite is to obtain the lock! The prerequisite is to obtain the lock! The sleep method calls ~ at any time

  • Threads calling wait need to wake up with notify, while sleep must set a timeout value.

  • Sleep does not release the lock after a thread calls wait.

    This is probably the most important difference between the two threads: Dog, Cat, and Wang. The threads initially acquire the lock of the toilet, but use the wait method to release the lock, allowing the maintenance worker to access the toilet. If sleep is called when the toilet is unavailable, the thread will not release the lock. This means that the maintenance worker will not be able to fix the toilet.

conclusion

  1. Threads need to communicate to work together to solve a complex problem.

  2. Mutable shared variables are a natural communication medium, but they must be used to ensure thread-safety. Volatile variables or synchronized are commonly used to ensure thread-safety.

  3. After a thread has acquired a lock, if the specified condition is not met, it should take the initiative to relinquish the lock, and then wait in a special waiting area until a thread completes the specified condition, and then notify the thread waiting for the condition to complete, let them continue to execute. This mechanism is wait/notify.

  4. Common pattern for waiting threads:

    synchronized(object) {processing logic (optional)while{object. Wait (); Processing logic (optional)}Copy the code

    It can be divided into the following steps:

    • Get the object lock.
    • If a condition is not met, the wait method of the lock object is called and the condition is still checked after being notified.
    • If the condition is met, the code continues to execute.
  5. General pattern for notification threads:

    synchronized(object) {Completion condition object. NotifyAll (); },Copy the code

    It can be divided into the following steps:

    • Get the lock of the object.
    • Completion conditions.
    • Notifies the waiting thread in the wait queue.
  6. The difference between ‘wait’ and ‘sleep

    • Wait is a member method of Object, while sleep is a static method of Thread.

    • A call to wait requires the lock to be acquired; a call to sleep does not.

    • Threads calling wait need to wake up with notify, while sleep must set a timeout value.

    • Sleep does not release the lock after a thread calls wait.

digression

Writing articles is tiring, and sometimes you feel that the reading is smooth, but it is actually the result of countless revisions behind. If you feel good please help to forward, thank you ~ here is my public number, there are more technical dry goods, from time to time pull a calf, welcome to pay attention to:

Small volumes

In addition, the author also wrote a short book on MySQL: How MySQL works: A link to understand MySQL from the root. The content of the volume is mainly from the perspective of small white, using popular language to explain some core concepts about MySQL advanced, such as record, index, page, table space, query optimization, transaction and lock, a total of about 300,000 or 400,000 words, with hundreds of original illustrations. MySQL > MySQL > MySQL > MySQL > MySQL > MySQL > MySQL > MySQL > MySQL