Thinking logic of computer programs (67) and thinking 67
The previous section introduced the problem of competing multiple threads to access the same resource and the solution synchronized. we mentioned that in addition to competition among multiple threads, mutual collaboration is often required, this section describes the basic mechanism of multi-thread collaboration in Java.
What scenarios need collaboration? What is wait/notify? How to use it? What is the implementation principle? What is the core of collaboration? How to implement various typical collaboration scenarios? Due to the large amount of content, we will introduce it in the last two sections.
Let's first take a look at the collaboration scenarios.
Scenarios of collaboration
There are many scenarios for collaboration between multiple threads, such:
- Producer/consumer collaboration mode: this is a common collaboration mode. Producer threads and consumer threads collaborate through the shared queue, and the producer puts data or tasks in the queue, the consumer obtains data or tasks from the queue. If the queue length is limited, the producer needs to wait when the queue is full, while the consumer needs to wait when the queue is empty.
- Start at the same time: similar to an athlete's game, the game starts at the same time after it hears a gun. In some programs, especially simulation programs, multiple threads are required to start at the same time.
- Wait for the end: the master-slave collaboration mode is also a common collaboration mode. The master thread splits the task into several subtasks and creates a thread for each subtask, the main thread needs to wait for each subtask to complete before continuing to execute other tasks.
- Asynchronous results: in the master-slave collaboration mode, it is often difficult to write a method to manually create sub-threads in the master thread. A common mode is to encapsulate the management of sub-threads into asynchronous calls, asynchronous calls return immediately, but the returned result is not the final result, but an object generally called Promise or Future, through which the final result can be obtained later.
- Meeting point: similar to a group tour of a school or company, there are several meeting points during the tour, such as the starting point. Each person comes to the meeting point from different places. After everyone arrives, they will carry out the next activity. In some procedures, for example, in parallel iteration computing, each thread is responsible for a part of computing, and then waits for other threads to finish at the aggregation point. After all threads arrive, the data and computing results are exchanged and the next iteration is performed.
We will explore how to implement these collaboration scenarios. Before that, we will first learn about the basic method of collaboration, wait/notify.
Wait/notify
We know that the root parent class of Java is Object. Java defines some basic methods for Thread collaboration in the Object class rather than the Thread class so that each Object can call these methods, there are two types of these methods: wait and notify.
There are two main wait methods:
public final void wait() throws InterruptedExceptionpublic final native void wait(long timeout) throws InterruptedException;
A time parameter, in milliseconds, indicates a maximum wait time. If the parameter is set to 0, it indicates an indefinite wait time. A parameter without a time parameter indicates an indefinite wait. Actually, wait (0) is called ). During the waiting period, the exception can be interrupted. If the exception is interrupted, InterruptedException will be thrown. We will introduce the exception in the next section and ignore it temporarily.
What does wait actually do? What is it waiting? As we said in the previous section, each object has a lock and a waiting queue. When a thread enters the synchronized code block, it tries to obtain the lock. If it cannot be obtained, it adds the current thread to the waiting queue, in fact, in addition to the lock wait queue, each object also has another waiting queue, indicating the condition queue, which is used for collaboration between threads. When wait is called, the current thread is put on the condition queue and blocked, indicating that the current thread cannot be executed. It needs to wait for a condition, which cannot be changed by itself and needs to be changed by other threads. When other threads change the condition, they should call the notify method of the Object:
public final native void notify();public final native void notifyAll();
Notify selects a thread from the condition queue, removes it from the queue, and wakes up. The difference between yyall and notify is that it will remove all threads in the condition queue and wake up all.
Let's look at a simple example. After a thread starts, it needs to wait for the main thread to give it instructions before executing an operation. The Code is as follows:
public class WaitThread extends Thread { private volatile boolean fire = false; @Override public void run() { try { synchronized (this) { while (!fire) { wait(); } } System.out.println("fired"); } catch (InterruptedException e) { } } public synchronized void fire() { this.fire = true; notify(); } public static void main(String[] args) throws InterruptedException { WaitThread waitThread = new WaitThread(); waitThread.start(); Thread.sleep(1000); System.out.println("fire"); waitThread.fire(); }}
In the sample code, there are two threads, one is the main thread, the other is WaitThread, and the condition variable for collaboration is fire. WaitThread calls wait when the variable is not true, the main thread sets the variable and calls notify.
Both threads need to access the collaborative variable fire, which is prone to race conditions. Therefore, the relevant code must be protected by synchronized. In fact, the wait/notify method can only be called within the synchronized code block. If the current thread does not hold the object lock when the wait/notify method is called, an exception java. lang. IllegalMonitorStateException will be thrown.
You may be wondering, if wait must be protected by synchronzied, how can another thread call the policy method that is also protected by synchronzied when wait is used? Does it need to wait for the lock? We need to further understand the internal process of wait. Although it is in the synchronzied method, the thread will release the object lock when calling wait. The specific process of wait is:
After a thread returns from a wait call, it does not mean that its waiting conditions are true. It needs to re-check its waiting conditions. The general call mode is:
Synchronized (obj) {while (the condition is not true) obj. wait ();... // The operation after the execution condition is met}
For example, the code in the above example is:
synchronized (this) { while (!fire) { wait(); }}
Calling condition y will wake up the thread waiting in the condition queue and remove it from the queue, but it will not release the object lock. That is to say, only after the synchronzied code block containing condition y is executed, the waiting thread will be returned from the wait call.
Simply put, the wait/notify method looks very simple, but it is often difficult to understand what wait is, and what the notify y method is, we need to know, they are related to a shared condition variable, which is maintained by the program itself. When the condition fails, the thread calls wait to enter the condition waiting queue, when another thread modifies the condition variable and calls notify, the wait thread needs to re-check the condition variable after waking up. From the perspective of multithreading, they collaborate around shared variables. From the perspective of the thread that calls wait, it blocks waiting for a condition to come true. When designing multi-threaded collaboration, we need to think clearly about the shared variables and conditions of collaboration, which is the core of collaboration. Next, we will further understand the wait/consumer y application through some scenarios. This section only introduces the producer/consumer model, and the next section introduces more models.
Producer/consumer model
In the producer/consumer mode, the shared variable of collaboration is a queue, where the producer places data in the queue. If it is full, wait is used, and the consumer obtains data from the queue, if the queue is empty, wait is used. We design the queue as a separate class. The Code is as follows:
static class MyBlockingQueue<E> { private Queue<E> queue = null; private int limit; public MyBlockingQueue(int limit) { this.limit = limit; queue = new ArrayDeque<>(limit); } public synchronized void put(E e) throws InterruptedException { while (queue.size() == limit) { wait(); } queue.add(e); notifyAll(); } public synchronized E take() throws InterruptedException { while (queue.isEmpty()) { wait(); } E e = queue.poll(); notifyAll(); return e; }}
MyBlockingQueue is a queue with a limited length. The length is transmitted by parameters of the constructor. There are two methods: put and take. Put is used by the producer. put data to the queue and wait when it is full. After the put is complete, notifyAll is called to notify potential consumers. Take is used by the consumer to fetch data from the queue. If it is null, wait will be used. After it is obtained, notifyAll will be called to notify potential producers.
We can see that both put and take call wait, but they have different purposes, or they have different waiting conditions. put waits for a queue that is not full, while the take waits for the queue not to be empty, but they all join the same conditional waiting queue. Because the conditions are different but the same waiting queue is used, you need to call yyall instead of calling notify, because notify can only wake up one thread. If it is the same thread, it cannot coordinate.
There can be only one conditional wait queue, which is the limitation of the Java wait/notify mechanism, which makes the analysis of the waiting condition complicated. We will introduce the explicit lock and condition in subsequent chapters, it can solve the problem.
A simple producer code is as follows:
static class Producer extends Thread { MyBlockingQueue<String> queue; public Producer(MyBlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { int num = 0; try { while (true) { String task = String.valueOf(num); queue.put(task); System.out.println("produce task " + task); num++; Thread.sleep((int) (Math.random() * 100)); } } catch (InterruptedException e) { } }}
The Producer inserts simulated task data into the shared queue. A simple sample consumer code is as follows:
static class Consumer extends Thread { MyBlockingQueue<String> queue; public Consumer(MyBlockingQueue<String> queue) { this.queue = queue; } @Override public void run() { try { while (true) { String task = queue.take(); System.out.println("handle task " + task); Thread.sleep((int)(Math.random()*100)); } } catch (InterruptedException e) { } }}
The sample code of the main program is as follows:
public static void main(String[] args) { MyBlockingQueue<String> queue = new MyBlockingQueue<>(10); new Producer(queue).start(); new Consumer(queue).start();}
Run this program and you will see the output of the producer and consumer threads alternate.
The MyBlockingQueue we implemented is mainly used for demonstration. Java provides special blocking queue implementation, including:
- Interface BlockingQueue and BlockingDeque
- Array-based implementation class ArrayBlockingQueue
- Implementation of LinkedBlockingQueue and LinkedBlockingDeque Based on linked lists
- Heap-based implementation class PriorityBlockingQueue
We will introduce these classes in subsequent chapters. In actual systems, we should consider using these classes.
Summary
This section describes the basic mechanism of thread-to-thread collaboration in Java, wait/notify. The key to collaboration is to understand what the shared variables and conditions of collaboration are. For further understanding, this section describes how to use wait/consumer y in producer/consumer mode.
In the next section, we will continue to explore other collaboration modes.
(As in other sections, all the code in this section is in the https://github.com/swiftma/program-logic)
----------------
For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.