Multithreading Series 5: Thread Status

Thread status in multi-thread series 5

Tip: This article mainly describes the six states of threads, and details the three methods that cause blocking: sleep, wait, notify, and join.

Article directory

  • Multithreading Series 5: Thread Status
  • 1. Thread status
    • 1) There are six states of threads
    • 2) Schematic diagram of thread status operation
  • 2. sleep method
    • 1) Introduction to sleep method
    • 2) Print the status of the thread during sleep
  • 3. synchronized lock
    • 1) synchronized modification
    • 2) Lock rules
      • Article 1
      • Article 2
      • Article 3
    • 3) Case demonstration
      • code
      • Analysis ***** Highlights ****
    • 4) Introduce synchronized
  • 4. wait and join
    • 1) join method
    • 2) wait and notify
      • Use of wait and notify
      • No parameter version
      • Version with parameters
      • notifyAll
      • Control three threads to execute sequentially

1. Thread status

1. Thread is the basic unit of operating system scheduling and execution
2. Status is an important attribute of threads. The state we are talking about is used to describe the thread being scheduled for execution.

1) There are six states of threads

1.NEW: The Thread object has been created, but start has not been called, and the PCB has not been created in the operating system kernel.

2.Runnable: Runnable. There are two situations in this state, namely, being executed on the CPU and in the ready queue. It has not yet been executed on the CPU and is ready to be executed on the CPU at any time.

3.TERMINATED: The kernel PCB of the operating system has been executed and destroyed, but the Thread object is still there

Three blocking states:
4.TIME_WAITING Call sleep
5.WAITING wait(notify) and join
6.BLOCKED Locking operation synchronized

2) Schematic diagram of thread status operation

1.TERMINATED: After the run method in the PCB in the operating system kernel is executed, the PCB is released, but the Thread object is still there.
2. It is because the life cycle in the application and kernel mode cannot be consistent. PCB is released but there is no guarantee that the t object will be released immediately.
3. Under normal circumstances, we believe that a thread can only statr once. At this time, the t thread can no longer be used, but it is still possible to call some properties and methods through t.

2. sleep method

1) Introduction to sleep method

The 1.sleep method is a class method of the Thread class, so it can be used by using Thread.sleep.

2. Putting threads to sleep essentially means that these threads will no longer participate in scheduling temporarily.

3. The sleep method needs to be wrapped with a try catch, because sleep may trigger an exception.

2) Print the status of the thread during sleep


Run results

1. It can be seen that the Thread object already exists and the PCB has not yet been created. The status of the thread at this time is NEW.
2. Through loop printing in the main thread, you can capture the sleep state of the thread and the state of the thread when it is running.
3. Since the scheduling strategy of the operating system is random scheduling and preemptive execution, we cannot control which one of the main thread and the t thread ends first. At this time, adding t.join to the main thread means that the main thread can code here. Wait for t thread to finish executing before continuing execution
4. After the t thread is executed, the status of the thread is TERMINATED.

3. synchronized lock

Clear: Locking is a thread locking an object.

1) synchronized modification

1. Modify ordinary methods
It can be placed in front of public or behind public. When synchronized modifies a normal method, the locked object at this time is this

2. Modify static methods
It is placed in the same position as the modified ordinary method, but the locked object at this time is the class object, which is the class name.class

3. Modify code blocks
synchronized can also modify the code block. The locked object can be specified arbitrarily. The lock is locked when entering the code block and the lock is released when exiting the code block.

2) Lock rules

Article 1

If two threads lock the same object, lock competition will occur on a first-come, first-served basis. After thread t1 obtains the lock, the other thread t2 can only block and wait. It is not until t1 releases the lock that the t2 thread can successfully acquire the lock.

Article 2

If two threads lock different objects, there will be no lock competition and both threads can acquire each other’s locks.

Article 3

If one thread locks and another thread does not lock, there will be no lock competition at this time.

3) Case demonstration

Code

Create two threads respectively and call the add method 50,000 times respectively. The expected value of count is 10,000. If synchronized is not added

Operation result: The value of number is uncertain at this time

Analysis ***** Key Points ****

1. First, let’s introduce the CPU registers
There is an important component in the CPU, called the cpu register. This register space is relatively small, but the access speed is very fast. The operations performed by the cpu are all performed on the data of the cpu register.

2.number + + operation is not atomic and can be divided into three steps
The so-called atoms refer to instructions that cannot be further divided.
The + + operation is not atomic and can be divided into three steps

1. Load first reads the memory value into the cpu register.
2. Add performs + 1 operation on the value of the CPU register
3. Write the value of the save cpu register back to the memory

3.Illustration:
1. Expected operation

2.! ! ! ! ! ! ! ! ! ! ! ! ! !

1. But since the + + operation is not atomic
2. The operating system scheduling strategy is random scheduling and preemptive execution.
3. Therefore, there may be countless situations. For example, after the t1 thread completes the load operation and switches the t2 thread process, dirty reading will occur at this time.

3. A situation where an error occurs
If the execution sequence of the code is as follows, a bug will occur

1. The t1 thread read the dirty data that the t2 thread has not had time to return.
2. Therefore, dirty reads appear

4) Introduction of synchronized

1. From the above analysis, it can be seen that the reason for the bug is that the count++ operation is not atomic and consists of three atomic instruction operations. Due to the random scheduling of the operating system, threads may be switched at any time.
2. In order to solve the above problems, we introduced synchronized, which essentially “converts” non-atomic operations into atomic ones.

Illustration:

1. Lock competition occurs only when the same object is locked. As shown in the figure above, it is because both try to lock the myCount object. Because t1 obtains the lock first, t2 can only wait until T2 can successfully lock after t1 releases the lock.
2. The reason for the empty diamond in the picture is that thread locking does not mean that the thread will not leave on the CPU. The operating system still schedules threads randomly, but other threads try to lock the object and can only enter blocking. wait.

4. wait and join

1) join method

1. When the thread is in join, the status of the thread is WAITING
2. The join method needs to throw an exception


Run results:

Calling t1.join in the main thread means that the main thread has to wait for thread t1 to finish executing before it can continue execution, and the main thread enters blocking.

2) wait and notify

1. Since the operating system scheduling thread is randomly scheduled and executed preemptively
2. You can use some provided APIs to allow threads to actively give up the CPU to make way for other threads.
3. The three main methods are wait notify and notifyAll. These three methods are all Objectivec methods.
4. The wait method and notify must appear in pairs.

But isn’t it the sleep method and the join method? Why do we need to introduce the wait method? ? ?
1. The sleep method only specifies a period of sleep time. It is difficult to estimate how much time A thread spends working.
2. Compared with the wait method, the join method can only block and wait forever. Only after a certain thread finishes executing, this thread can continue to execute. The wait method allows thread A to finish 50% of its work and then wake up thread B, which is more flexible and convenient.

The use of wait and notify

1. If wait and notify use different objects, notify will not wake up wait.
2.wait and notify must be locked, and the locked objects must also be the same, that is, the four objects used must be the same.
3.wait is divided into a version with parameters and a version without parameters. The version without parameters can only wait until it receives the notification. The version with parameters will not notify and will directly retry to acquire the lock if the maximum waiting time is exceeded. , continue execution after successfully acquiring the lock

The wait operation has three steps:
1. Release the lock first
2. Enter blocking wait
3. After receiving the notification, try to acquire the lock again, and continue execution after successfully acquiring the lock.

No parameter version


Run results:

1.notify can only wake up threads waiting on the same object
2. After t1 start, you need to sleep for a while to prevent notification from not being waited for, making notify an invalid notification.

Version with parameters


Run results:

notifyAll

 public static void main(String[] args) throws InterruptedException {<!-- -->
        Object object = new Object();
        Thread t1 = new Thread(() -> {<!-- -->
            try {<!-- -->
                synchronized (object) {<!-- -->
                    object.wait();
                }
            } catch (InterruptedException e) {<!-- -->
                e.printStackTrace();
            }
            System.out.println("t1 thread is awakened");
        });
        Thread t2 = new Thread(() -> {<!-- -->
            try {<!-- -->
                synchronized (object) {<!-- -->
                    object.wait();
                }
            } catch (InterruptedException e) {<!-- -->
                e.printStackTrace();
            }
            System.out.println("t2 thread is awakened");
        });
        Thread t3 = new Thread(() -> {<!-- -->
            try {<!-- -->
                synchronized (object) {<!-- -->
                    object.wait();
                }
            } catch (InterruptedException e) {<!-- -->
                e.printStackTrace();
            }
            System.out.println("t3 thread is awakened");
        });
        Thread t4 = new Thread(() -> {<!-- -->
            synchronized (object) {<!-- -->
                object.notifyAll();
            }
        });
        t1.start();
        t2.start();
        t3.start();
        Thread.sleep(500);
        t4.start();
    }

t1 t2 t3 is the waiting thread and t4 is the waking thread. When notifyAll is used, all threads waiting for the same object can be awakened.

Run results:

When t4 notify, the waiting thread on the same object will be randomly awakened.

Run results:

Control three threads to execute in sequence

Create three threads. Due to the random scheduling and preemptive execution of the operating system, the order cannot be controlled. Through notify wait, the three threads can run in sequence as expected.

 public static void main(String[] args) throws InterruptedException {<!-- -->
        Object obj1 = new Object();
        Object obj2 = new Object();
        Define two objects
        t1 and t2 wait and notify use obj1
        t2 and t3 wait and notify use obj2
        Thread t1 = new Thread(() -> {<!-- -->
            System.out.println("T1 thread was executed");
            synchronized (obj1) {<!-- -->
                obj1.notify();
            }
        });
        Thread t2 = new Thread(() -> {<!-- -->
            try {<!-- -->
                synchronized (obj1) {<!-- -->
                    obj1.wait();
                }
            } catch (InterruptedException e) {<!-- -->
                e.printStackTrace();
            }
            System.out.println("T2 thread was executed");
            synchronized (obj2) {<!-- -->
                obj2.notify();
            }
        });
        Thread t3 = new Thread(() -> {<!-- -->
            try {<!-- -->
                synchronized (obj2) {<!-- -->
                    obj2.wait();
                }
            } catch (InterruptedException e) {<!-- -->
                e.printStackTrace();
            }
            System.out.println("T3 thread was executed");
        });
        t2.start();
        t3.start();
        
        Let t1 and t2 be created first and enter the blocking state to prevent t1 notify from becoming an invalid notification.
    
        Thread.sleep(500);
        t1.start();
    }

Running result: