I am a fu, the author of the public account JavaClub, a back end technology on the way to touch the plate rolling programmer, in the way of progress, mutual encourage! Articles are included in JavaSharing, including Java technology articles, interview guides, and resource sharing.
The skills are as follows:
- Use Wait /notify to communicate between threads
- The life cycle of a thread
- Implementation of the producer/consumer pattern
- The use of the join method
- Use of the ThreadLocal class
Interthread communication
3.1 Use Wait/Notify to Implement Communication between Threads
3.1.1 Implementation of wait/notification mechanism
What is a wait/notification mechanism
Wait/notification mechanisms are everywhere in our lives, such as during a meal, as shown in the picture below:
-
The time it takes a chef to finish a dish is uncertain, so is the time it takes the chef to put the dish on the “dish counter”.
-
The time it takes for the waiter to pick up the food depends on the cook, so the waiter has a “wait” state.
-
When the chef puts the dish on the “dish pass table”, which is equivalent to notify, the waiter can take the dish to the diner.
This process gives rise to a wait/notify mechanism.
In technical terms:
The wait/notification mechanism refers to that thread A invokes the wait() method of object O to enter the wait state, while thread B invokes the notify()/notifyAll() method of object O. After receiving the notification, thread A exits the wait queue, enters the running state, and performs subsequent operations. The two threads interact through object O, and the relationship between wait() and notify()/notifyAll() on the object is like a switch signal to complete the interaction between the wait and notify.
Implementation of wait/notification mechanism
The wait() method does:
To block the current thread, the thread must acquire an object-level lock on the object before calling wait(), which means that the wait() method can only be called within a synchronized method or block of synchronized code. After executing wait(), the current thread releases the lock.
NotifyAll () notifyAll()
If more than one thread is waiting, the thread planner randomly selects one of the threads in wait() state to notify, and causes it to acquire the object lock of the object.
To be clear: After executing notify(), the current thread will not release the lock immediately, nor will a thread in wait() acquire the lock immediately. The lock will not be released until the notify() thread finishes executing the program, i.e. exits the synchronized block. Only threads in wait() state can acquire the object lock.
NotifyAll (), with emphasis on notify(), is also called in a synchronized method or block of synchronized code, that is, a notifyAll() must obtain an object-level lock on the object before it can be called.
To sum up wait and notify in one sentence: Wait causes a thread to stop running, while notify causes a stopped thread to continue running.
The following code implements an example:
Create mylist.java as follows:
public class MyList { private static List list=new ArrayList(); public static void add(){ list.add("anyString"); } public static int size(){ return list.size(); }}Copy the code
Mythread1.java, mythread2.java, MyThread3.
public class MyThread1 extends Thread { private Object lock; public MyThread1(Object lock) { this.lock = lock; } @Override public void run() { try { synchronized (lock) { if (MyList.size() ! = 5) {system.out.println (" wait time=" + system.currentTimemillis ()); lock.wait(); System.out.println(" End wait time=" + system.currentTimemillis ()); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class MyThread2 extends Thread { private Object lock; public MyThread2(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { for (int i = 0; i < 10; i++) { MyList.add(); if (MyList.size() == 5) { lock.notify(); System.out.println(" notification sent "); } system.out. println(" add "+ (I + 1) +" element!!" ); }}}}Copy the code
Create the Test class test.java
public class Test { public static void main(String[] args) { Object lock=new Object(); MyThread1 myThread1=new MyThread1(lock); myThread1.start(); MyThread2 myThread2=new MyThread2(lock); myThread2.start(); }}Copy the code
The running results of the program code are as follows:
Wait time=1618832467129 add 1 element!! 2 elements added!! Added 3 elements!! 4 elements added!! Notification has been sent to add 5 elements!! 6 elements added!! 7 elements added!! 8 elements added!! 9 elements added!! Added 10 elements!! The end of the wait time = 1618832467130
From the results of the run, this also indicates that the notify() method does not immediately release the lock.
3.2 Thread life cycle
Thread life cycle transition diagram
Thread state
A thread is always in one of five states from creation to execution: New state, ready state, running state, blocked state, dead state.
-
Thread Thread = new Thread(); Thread Thread = new Thread();
-
Runnable state: When a thread is new, another thread calls the object’s start() method, thread.start(). At this point, the thread is in the “Runnable thread pool”, waiting for the CPU to use it, and is ready to be called by the CPU. A process that enters the ready state has all the resources it needs to run except the CPU.
-
Running: The thread obtains CPU permission and starts executing. Note: A thread can only go from ready to run.
-
Bloacked: BLOCKED is when a thread gives up access to the CPU for some reason and stops running until it is ready to go back to running.
There are three types of blocking:
(1) Wait blocking: A running thread performs wait(), which releases all resources occupied by the thread, and the JVM places the thread in the “wait pool.” This state cannot be woken up automatically. Other threads must call notify() or notifyAll() to wake up. (2) Synchronized blocking: When a running thread acquires a synchronized lock that is occupied by another thread, the JVM pools that thread into a synchronized lock pool.
(3) Other blocking: the thread will enter the blocking state by calling its sleep() or join() or issuing an I/O request. The thread returns to the ready state when the sleep() state times out, when the join() wait thread terminates or times out, or when I/O processing is complete.
- Dead: a thread terminates its life cycle when it finishes executing or exits the run method due to an exception.
Blocking thread methods:
-
Wait (),notify (), and notifyAll() are methods that are used together and belong to Object. The purpose of wait is to make the current thread release the lock it holds and enter the wait state. Notify and notifyAll wake up the waiting thread on the current object.
-
The sleep() and yield() methods belong to the Thread class. The sleep() function is to put the current Thread to sleep(the executing Thread voluntarily gives up the CPU so that the CPU can perform other tasks). That is, the current Thread goes from the “running” state to the blocked state, but still holds the object lock. When the delay time expires, the thread reblocks and becomes ready to wait for the CPU’s scheduled execution.
-
Yield () acts as a concession, allowing the current thread to move from the running state to the ready state, “allowing other waiting threads to acquire execution rights. However, there is no guarantee that other threads will gain execution rights after the current thread calls yield(), or that the current thread may return to the” running state “and continue running.
The difference between wait () and sleep() :
Sleep () holds the lock while wait() releases the lock. 2. Wait can only be used in synchronized methods and blocks, while sleep can be used anywhere. 3. Sleep must catch exceptions while WAIT does not
3.3 Realization of producer/consumer model
The producer-consumer problem, also known as the Bounded buffer problem, is a classic case of multi-thread synchronization. The producer generates a certain amount of data and puts it in the buffer, and then repeats the process. At the same time, consumers consume this data in the buffer. There must be synchronization between producers and consumers to ensure that producers do not put data in when the buffer is full and consumers do not consume data when the buffer is empty. Imperfect solutions are prone to deadlock situations where processes are waiting to wake up.
Solutions to the producer/consumer problem fall into two categories
(1) Adopt some mechanism to protect the synchronization between producers and consumers; (2) Build a pipeline between producers and consumers. The first method has higher efficiency, and easy to implement, the code can be controlled better, belongs to the common mode. The second kind of pipeline buffer is not easy to control, the transmitted data object is not easy to encapsulate, etc., so it is not practical. Therefore, this article covers only the producer/consumer issues of synchronization implementation.
The core of the synchronization problem is
How to ensure the integrity of the same resource when accessed concurrently by multiple threads. The common synchronization method is to use a signal or locking mechanism to ensure that the resource can be accessed by at most one thread at any time. Java language implements complete objectification in multithreaded programming and provides good support for synchronization mechanism. There are four methods that support synchronization in Java, the first three of which are synchronous methods and a pipe method.
Wait ()/notify(
(2) await()/signal() methods
(3) BlockingQueue blocks the queue
(4) PipedInputStream/PipedOutputStream
Let’s implement the producer and consumer patterns with wait()/notify() :
Code scenarios:
When the buffer is full, the producer thread stops executing, gives up the lock, and leaves itself in an equal state for other threads to execute. When the buffer is empty, the consumer thread stops executing, waives the lock, and leaves itself in an equal state for other threads to execute.
When a producer puts a product into the buffer, it sends an executable notification to other waiting threads and waives the lock, leaving itself in a waiting state. When a consumer pulls a product out of the buffer, it sends an executable notification to other waiting threads and waives the lock, leaving itself in the wait state.
Code implementation:
Create repository storage.java
Public class Storage {private final int MAX_SIZE = 10; Private LinkedList<Object> list = new LinkedList<>(); Public void produce() {synchronized (list) {while (list.size() + 1 > MAX_SIZE) {system.out.println (" [list] + ") Thread.currentthread ().getName() + ""; try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.add(new Object()); System.out.println(" [producer] + thread.currentThread ().getName() + "); list.notifyAll(); }} public void consume() {synchronized (list) {while (list.size() == 0) {system.out.println (" [consumer "+)}} public void consume() {synchronized (list) {while (list.size() == 0) {system.out.println (" [consumer" + Thread.currentthread ().getName() + ""; try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.remove(); System.out.println(" [consumer] + thread.currentThread ().getName() + "); list.notifyAll(); }}}Copy the code
Create a Producer thread (producer.java) and a Consumer thread (consumer.java) as follows:
public class Producer implements Runnable { private Storage storage; public Producer(){} public Producer(Storage storage){ this.storage = storage; } @Override public void run(){ while(true){ storage.produce(); }}}Copy the code
public class Consumer implements Runnable{ private Storage storage; public Consumer(){} public Consumer(Storage storage){ this.storage = storage; } @Override public void run(){ while(true){ storage.consume(); }}}Copy the code
Create the test class testpc.java
public class TestPc { public static void main(String[] args) { Storage storage = new Storage(); Thread p1 = new Thread(new Producer(storage)); P1. Elegantly-named setName (" zhang "); p1.start(); Thread c1=new Thread(new Consumer(storage)); c1.start(); C1. Elegantly-named setName (" li si "); }}Copy the code
Partial results of the program run:
Consumption of a product, consumers li si 】 【 now 8 consumption consumer li si 】 【 a product inventory, the inventory consumption of a product, consumers li si 】 【 7 now inventory consumption consumer li si 】 【 a product, an inventory consumption consumer li si 】 【 5 products, is now inventory 4 production producer zhang SAN 】 【 a product, We have 5 producers producing a product, we have 6 producers producing a product, we have 7 producers producing a product, we have 8 producers producing a product, we have 9 producers producing a product, Present inventory 10 [producer Zhang SAN] warehouse is full [consumer Li Si] consumption of a product, present inventory 9 [consumer Li Si] consumption of a product, present inventory 8 [consumer Li Si] consumption of a product, present inventory 7
3.4 Use of the join method
In many cases, the main thread creates and starts the child thread, and if a lot of computation is done in the child thread, the main thread often ends before the child thread. The main thread waits for the child thread to complete before terminating. For example, when a child thread processes data, the main thread uses join() to retrieve the value in the data.
The Join () method waits for the thread object to be destroyed.
Create test myJoinThread. Java code:
public class MyJoinThread extends Thread{ @Override public void run() { int secondValue= (int) (Math.random() * 10000); System.out.println(secondValue); try { Thread.sleep(secondValue); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code
Create test class testJoin.java
public class TestJoin { public static void main(String[] args) throws InterruptedException { MyJoinThread myJoinThread=new MyJoinThread(); myJoinThread.start(); //myJoinThread.join(); System.out.println(" I think I will execute myJoinThread when it is finished, the answer is not sure "); }}Copy the code
The code runs as follows:
I think I’ll execute myJoinThread when it’s done. The answer is undefined 9618
Myjointhread.join () = joinThread.join ()
82 I think I will execute myJoinThread when it is finished. The answer is not sure
Join () causes myJoinThread to execute the run() task normally, while main blocks indefinitely, waiting for myJoinThread to destroy before continuing to execute the main thread.
The join() method has the effect of queuing threads to run, somewhat like synchronous running.
The difference between join() and synchronized is that join() uses wait() internally, while synchronized uses the object listener principle.
The difference between join() and sleep(long) methods
The join(long) function is internally implemented using the Wait (long) method, which releases locks.
Method join(long) source code is as follows:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; }}}Copy the code
As you can see from the source code, when the wait(long) method is executed, the lock of the current thread is released, and other threads can call synchronized methods in this thread.
The thread.sleep (long) method does not release the lock.
3.5 Use of the ThreadLocal class
We know that we can use the public static variable to share the value of a variable. What if every thread has its own shared variable? The provision of ThreadLocal in the JDK addresses just such a problem.
The ThreadLocal class is designed to bind values for each thread. The ThreadLocal class can be likened to a global data box that stores private data for each thread.
Create the run.java class with the following code:
public class run { private static ThreadLocal threadLocal=new ThreadLocal(); Public static void main(String[] args) {if (threadLocal.get()==null){system.out.println (); Threadlocal.set (" my value "); } system.out.println (thread.currentThread ().getName()+" threadLocal.get() "); }}Copy the code
The code runs as follows:
Never let go of the value main thread: my value
The first call to a threadLocal object’s get method returns null, and the value is printed on the console after being assigned by a call to set(). The values of different threads can be stored in the ThreadLocal class.
Verify the isolation of thread variables
Create a ThreadLocalTest project with tools. Java like this:
public class Tools {
public static ThreadLocal local=new ThreadLocal();
}
Copy the code
Create thread class mythread1.java, mythread2.java:
public class MyThread1 extends Thread { @Override public void run() { for (int j = 0; j < 5; j++) { Tools.local.set(j+1); System.out.println(Thread.currentThread().getName()+"get value:"+Tools.local.get()); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class MyThread2 extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { Tools.local.set(i+1); System.out.println(Thread.currentThread().getName()+"get value:"+Tools.local.get()); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }}}}Copy the code
Create the Run.java test class
public class run { public static void main(String[] args) { MyThread1 myThread1=new MyThread1(); MyThread1. Elegantly-named setName (" myThread1 threads "); myThread1.start(); MyThread2 myThread2=new MyThread2(); MyThread2. Elegantly-named setName (" myThread2 threads "); myThread2.start(); }}Copy the code
Program running results:
MyThread1 get value:1 myThread2 Get value:1 myThread2 Get value:2 myThread1 get value:2 myThread1 get value:3 MyThread2 thread Get value:3 myThread2 thread get value:4 myThread1 thread Get value:4 myThread1 thread Get value:5 myThread2 thread get value:5
Even though both threads fetch a set() value from local, each thread still fetches its own data.
Article Reference:
The core technology of Java multi-thread programming blog.csdn.net/ldx19980108… Blog.csdn.net/MONKEY_D_ME…
This is the end of today’s sharing. If you think this article is good, share it, like it, and read it again, so that more people can see it
Welcome to follow the personal public account “JavaClub”, regularly share some technical dry goods for you.