In the last article, we introduced the basis of threading. I believe everyone has a general understanding. This article is about thread sharing and collaboration. Let's have a look!
1, Sharing between threads
Synchronized
synchronized is a keyword in Java. It is a kind of synchronous lock. It modifies the following objects:
1. Modify a code block. The modified code block is called a synchronization statement block. Its scope of action is the code enclosed in braces {}, and the object of action is the object calling the code block; 2. Modify a method. The modified method is called a synchronous method. Its scope of action is the whole method, and the object of action is the object calling the method; 3. Modify a static method, its scope of action is the whole static method, and the object of action is all objects of this class; 4. Modify a class. Its scope of action is the part enclosed in parentheses after synchronized. The main object is all objects of this class.
volatile
Volatile is arguably the lightest synchronization mechanism provided by the Java virtual machine. But it is not easy to understand correctly, and many programmers will use synchronized when they encounter thread safety problems in concurrent programming. The JAVA memory pattern tells us that each thread will copy the shared variables from the main memory to the working memory, and then the execution engine will operate and process based on the data in the working memory. When will a thread write to main memory after operating in working memory? There is no provision for ordinary variables at this time, and special conventions are given to the Java virtual machine for volatile modified variables. The modification of volatile variables by threads will be immediately perceived by other threads, that is, there will be no dirty reading of data, so as to ensure the "visibility" of data.
Now we have a general impression that the variable modified by volatile can ensure that each thread can obtain the latest value of the variable, so as to avoid dirty data reading.
ThreadLocal
threadlocal is easy to use
static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>(); sThreadLocal.set() sThreadLocal.get()
threadlocal is an internal thread storage class that can store data in the specified thread. After data storage, only the specified thread can get the stored data. The official explanation is as follows.
/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). */
ThreadLocal provides the ability for threads to store variables in memory. The difference between these variables is that the variables read by each thread are corresponding and independent of each other. The corresponding value of the current thread can be obtained through the get and set methods.
As an inappropriate metaphor, ThreadLocal is equivalent to maintaining a map. The key is the current thread and the value is the object to be stored.
The metaphor here is inappropriate. In fact, the static internal class ThreadLocalMap of ThreadLocal maintains an array table for each Thread. ThreadLocal determines an array subscript, which is the corresponding location of value storage..
As a data storage class, the key point is in the get and set methods.
2, Collaboration between threads
1. Implementation of waiting and notification mechanism
wait() method
wait() is a method of the Object class. Its function is to make the thread currently executing the wait method wait. This method puts the current thread into the "pre execution queue" and stops execution at the code line where wait() is located. Execution cannot continue until it is notified or interrupted. The thread must get the Object lock of the Object, that is, the wait() method can only be invoked in the synchronization method or the synchronous method block. After executing the wait() method, the current thread releases the Object lock which is owned. If wait() does not hold the Object lock, it will throw the IllegalMonitorStateException exception.
notify() method
notify() is a method of Object class. The function is to make the stopped thread continue to run, and also to call it in synchronous method or synchronous block. This method is used to notify other threads that may be waiting for the object lock of the object. If there are multiple threads waiting, the thread planner randomly selects a thread in the wait state and sends a notification notify to it. However, the notified thread will not immediately execute the code behind the wait, because the thread using notify will not immediately release the lock, Therefore, the notified thread will not get the lock immediately. If notify is called without holding an object lock, an IllegalMonitorStateException is thrown
class MyThread1 extends Thread { private Object lock; public MyThread1(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " start wait time = " + System.currentTimeMillis()); try { //wait stops thread thread1 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " end wait time = " + System.currentTimeMillis()); } } } class MyThread2 extends Thread { private Object lock; public MyThread2(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " start notify time = " + System.currentTimeMillis()); //Thread thread2 causes stopped thread1 to continue running lock.notify(); System.out.println(Thread.currentThread().getName() + " end notify time = " + System.currentTimeMillis()); } } } public class Test extends Thread { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); MyThread1 thread1 = new MyThread1(lock); thread1.start(); Thread.sleep(2000); MyThread2 thread2 = new MyThread2(lock); thread2.start(); } }
The result is:
AAA start wait time = 1540528981658 BBB start notify time = 1540528983660 BBB end notify time = 1540528983661 AAA end wait time = 1540528983661
Briefly analyze the whole process. Thread AAA first executes the run() method, obtains the lock object lock, outputs a line, and then executes lock The wait () method means that the thread AAA releases the object lock, and then the thread AAA holding the object lock enters the waiting state, but the thread AAA is still in the synchronized synchronization block; Because the thread AAA stopped, the thread BBB started executing the run() method to get the lock object lock and output a row, then called lock.. Notify() wakes up the thread AAA waiting for the object lock to enter the ready state, but the thread BBB does not release the object lock immediately, but continues to execute the remaining methods in its own synchronization methods. Only after the thread BBB executes the synchronized synchronization block can the object lock be released. At this time, the awakened thread AAA can regain the object lock, and then execute the code after the wait() method
The whole process is shown in the figure. In short, thread AAA is waiting, and then thread BBB wakes up thread AAA with notify, but does not release the lock immediately. The lock is not released until thread BBB executes the synchronization block. At this time, thread AAA obtains the lock and starts to execute the method after wait
It should be noted that the wait() method makes the thread that owns the Object lock wait temporarily, which has nothing to do with who owns the Object lock. For example, in this example, thread AAA owns the Object lock of the Object object Object. At the same time, call the wait() method with the Object lock in the run() method, and then the Object lock is released. Note, The Object lock held by thread AAA is released because thread AAA is in the synchronization block. As a result, thread AAA enters the waiting state. This is very important!!!
At the same time, in the synchronous code block, the wait, nofity or notifyAll method of the lock object must be called. In the waiting thread, the object calling the wait() method must be the object in the bracket (synchronized), and in the notification thread, the object calling the notify() method must also be the object in the bracket, if not, An IllegalMonitorStateException is thrown
notify() can wake up only one thread
If multiple threads are waiting, the notify() method can wake up only one thread randomly, and other threads that are not awake are still waiting. However, the notity() method can be called multiple times to wake up multiple threads randomly
//Service2 method class Service2 { public void testMethod(Object lock) { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " beg1in wait " + System.currentTimeMillis()); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " end wait " + System.currentTimeMillis()); } } }
Create three threads ThreadA6, ThreadB6, and ThreadC6
class ThreadA6 extends Thread { private Object lock; public ThreadA6(Object lock) { this.lock = lock; } @Override public void run() { Service2 service2 = new Service2(); service2.testMethod(lock); } } class ThreadB6 extends Thread { private Object lock; public ThreadB6(Object lock) { this.lock = lock; } @Override public void run() { Service2 service2 = new Service2(); service2.testMethod(lock); } } class ThreadC6 extends Thread { private Object lock; public ThreadC6(Object lock) { this.lock = lock; } @Override public void run() { Service2 service2 = new Service2(); service2.testMethod(lock); } }
Method of randomly waking up a waiting thread
class NotifyOne extends Thread { private Object lock; public NotifyOne(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("NotifyOne"); lock.notify(); } } }
Multiple calls can randomly wake up multiple waiting threads
class NotifyMulti extends Thread { private Object lock; public NotifyMulti(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("NotifyMulti"); lock.notify(); lock.notify(); lock.notify(); lock.notify(); lock.notify(); } } }
test method
public class Test2 { public static void main(String[] args) throws InterruptedException { Object lock = new Object(); ThreadA6 threadA6 = new ThreadA6(lock); threadA6.start(); ThreadB6 threadB6 = new ThreadB6(lock); threadB6.start(); ThreadC6 threadC6 = new ThreadC6(lock); threadC6.start(); Thread.sleep(2000); NotifyOne notifyOne = new NotifyOne(lock); notifyOne.start(); /*NotifyMulti notifyMulti = new NotifyMulti(lock); notifyMulti.start();*/ } }
The result is:
Thread-0 beg1in wait 1540536524678 Thread-1 beg1in wait 1540536524679 Thread-2 beg1in wait 1540536524679 notifyOne Awakened a thread 1540536526679 Thread-0 end wait 1540536526679
Since only one thread can be awakened, the other two threads that are still in the waiting state are in the waiting state forever because they are not awakened. If the annotated statement is called, multiple threads can be awakened
Thread-0 beg1in wait 1540536666626 Thread-2 beg1in wait 1540536666626 Thread-1 beg1in wait 1540536666626 NotifyMulti Thread-0 end wait 1540536668627 Thread-1 end wait 1540536668627 Thread-2 end wait 1540536668627
notifyAll() can wake up multiple threads
Because we don't know how many threads are waiting, we can't keep calling the notify() method, which will be very troublesome. Therefore, we can use the notifyAll() method to wake up all the waiting methods
When interrupt method encounters wait method
When the thread is in the wait() state, an InterruptedException exception will occur when calling the interrupt() method of the thread object
class Service5 { public void testMethod(Object lock) { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " be1gin wait " + System.currentTimeMillis()); try { lock.wait(); System.out.println(Thread.currentThread().getName() + " end wait " + System.currentTimeMillis()); } catch (InterruptedException e) { System.out.println("exception occurred..."); e.printStackTrace(); } } } public void testMethod2(Object lock) { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " begin 2"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " end 2"); } } } class ThreadB5 extends Thread { private Object lock; public ThreadB5(Object lock) { this.lock = lock; } @Override public void run() { Service5 service5 = new Service5(); service5.testMethod2(lock); } } public class ThreadA5 extends Thread { private Object lock; public ThreadA5(Object lock) { this.lock = lock; } @Override public void run() { Service5 service5 = new Service5(); service5.testMethod(lock); } public static void main(String[] args) throws InterruptedException { Object lock = new Object(); ThreadA5 threadA5 = new ThreadA5(lock); threadA5.start(); threadA5.interrupt(); Thread.sleep(2000); ThreadB5 threadB5 = new ThreadB5(lock); threadB5.start(); } }
The result is:
Thread-0 be1gin wait 1540534325308 exception occurred... java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at edu.just.Service5.testMethod(ThreadA5.java:10) at edu.just.ThreadA5.run(ThreadA5.java:60) Thread-1 begin 2 Thread-1 end 2
It can be seen that when an error is reported, the code after the wait() method is also executed because an exception is encountered during the execution of the synchronous code block, resulting in the termination of the thread and the release of the lock. At this time, if another thread holds the object lock, the code behind the wait will not be executed, but will directly report an error
Notification time
wait(long) method
If there are parameters in the wait() method, it means that if no other thread wakes up the waiting thread within a period of time, the waiting thread will wake up automatically after this time
-
If you wake up within the specified time, the code of other threads will be executed first, and then the code after wait will be executed
-
If you wake up outside the specified direct, you will execute the code after wait first and execute the code of other threads
public class MyRunnable { private static Object lock = new Object(); private static Runnable runnable = new Runnable() { @Override public void run() { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " wait begin time " + System.currentTimeMillis()); try { //The thread is automatically awakened after 1 s lock.wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " wait end time " + System.currentTimeMillis()); } } }; private static Runnable runnable1 = new Runnable() { @Override public void run() { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " wait begin time2 " + System.currentTimeMillis()); try { lock.wait(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " wait end time " + System.currentTimeMillis()); } } }; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(runnable); thread.setName("AAA"); thread.start(); //Thread AAA is awakened within the specified time statement 1 Thread.sleep(1100); //Thread AAA is awakened outside the specified time statement 2 // Thread.sleep(900); Thread thread2 = new Thread(runnable1); thread2.setName("BBB"); thread2.start(); } }
Comment out statement 2 first, and the result is:
AAA wait begin time 1540538435802 AAA wait end time 1540538436802 BBB wait begin time2 1540538436902 BBB wait end time 1540538436903
You can see that thread AAA wakes up automatically before it is manually awakened by thread BBB, so you directly execute the method behind wait
When executing statement 2, comment statement 1. The result is:
AAA wait begin time 1540538528885 BBB wait begin time2 1540538529784 BBB wait end time 1540538529784 AAA wait end time 1540538529885
At this time, thread BBB wakes up thread AAA before thread AAA is automatically awakened. At this time, the code of thread BBB is executed first, and the code behind thread AAA wait() method is executed
The difference between wait() and sleep() methods
The wait() method is very similar to the sleep() method. A comparison can be made below:
-
wait is a member variable of the Object class, and sleep is a static method of the Thread class
-
You need to obtain the object lock before calling the wait method, but you don't need to obtain the object lock before calling the sleep method
-
The thread calling the wait method needs to wake up with notify, and the sleep method must set the timeout value
-
After calling the wait method, the thread will release the lock first, while the sleep method will not release the lock
2. join method
The following is the schematic diagram of join
Function demonstration
public class JoinDemo implements Runnable{ public void run() { System.err.println("join thread demo "); } public static void main(String[] args) throws Exception { System.err.println("main thread start... "); Runnable r = new JoinDemo(); Thread t = new Thread(r); t.setName("ibli joinTest ..."); t.start(); // t.join(); System.err.println("main thread end... "); } }
t.join(); Note out that one possible result of execution is as follows:
main thread start... main thread end... join thread demo
There may also be this result:
main thread start... join thread demo main thread end...
However, remove the comments and the results are as follows:
main thread start... join thread demo main thread end...
This is a very simple demo, and the effect is obvious. When the main thread calls t.join(), it will block its current thread. The main thread can continue to execute only when the execution of T thread reaches the completion state.
Let's take a look at how join() sets the timeout:
public class JoinDemo implements Runnable{ public void run() { System.err.println("join thread demo "); try { // Thread sleep 4s Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } List<String> strings = null; System.err.println(strings.get(0)); } public static void main(String[] args) throws Exception { System.err.println("main thread start... "); Runnable r = new JoinDemo(); Thread t = new Thread(r); t.setName("ibli joinTest ..."); t.start(); // However, the timeout of the main thread join is 1s t.join(1000); System.err.println("main thread end... "); } }
Execution effect:
main thread start... join thread demo main thread end... Exception in thread "ibli joinTest ..." java.lang.NullPointerException at com.ibli.threadTest.api.JoinDemo.run(JoinDemo.java:14) at java.lang.Thread.run(Thread.java:748)
join() source code
First, the join(0) method will be called, which is actually an overloaded method of the join;
public final void join() throws InterruptedException { join(0); }
The following is the core implementation of join:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; // First, check whether the parameters are legal if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } // If the join method has no parameters, it is equivalent to calling the wait method directly if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
The following is the source code of the isAlive method
public final native boolean isAlive();
This is a local method to determine whether the current thread is active. What is active state? The active state is that the thread has been started and not terminated. A thread is considered "alive" when it is running or ready to start running.
-
Here's a point to note: why does the join block the main thread rather than the child thread?
-
The reason why I don't understand is that the method of blocking the main thread is used in the instance of previousthread, which makes people mistakenly think that the previousthread thread should be blocked. In fact, the main thread will hold the lock of the previousThread object and then call the wait method to block it, and the caller of this method is in the main thread. Therefore, the main thread is blocked.
-
In fact, the core of the join() method is wait (). Calling t.join() in the main thread is equivalent to adding new JoinDemo () in the main method. wait(); Is the same effect; Here, it's just that the wait method is written in the method of the child thread.
-
Again, the function of the join method is to block the main thread, wake up the main thread by the sub thread after the sub thread is executed, and then continue to execute the logic after the main thread calls the t.join() method.
In fact, you can look at the source code implementation of the JVM, thread In the cpp file, there is a code:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) { // Notify waiters on thread object. This has to be done after exit() is called // on the thread (if the thread is the last thread in a daemon ThreadGroup the // group should have the destroyed bit set before waiters are notified). ensure_join(this); }
Which calls ensure_join method
static void ensure_join(JavaThread* thread) { // We do not need to grap the Threads_lock, since we are operating on ourself. Handle threadObj(thread, thread->threadObj()); assert(threadObj.not_null(), "java thread object must exist"); ObjectLocker lock(threadObj, thread); // Ignore pending exception (ThreadDeath), since we are exiting anyway thread->clear_pending_exception(); // Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED. java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED); // Clear the native thread instance - this makes isAlive return false and allows the join() // to complete once we've done the notify_all below //This is to clear the native thread. This operation will cause the isAlive() method to return false java_lang_Thread::set_thread(threadObj(), NULL); // Wake up the waiting thread here lock.notify_all(thread); // Ignore pending exception (ThreadDeath), since we are exiting anyway thread->clear_pending_exception(); }
In the JVM's code, lock is called at the end of thread execution notify_ All (thread) method to wake up all threads in wait
Usage scenario
-
For example, when we use Callable to execute asynchronous tasks, we need to call the join method when the main thread processes the return value of the task;
-
There are also scenarios where you want threads to execute sequentially;
Comparison between join() method and sleep()
Let's talk about the sleep method first:
-
Let the current thread sleep for a specified time.
-
The accuracy of sleep time depends on the system clock and CPU scheduling mechanism.
-
If the sleep method is invoked in the synchronous context, no other thread can enter into the current synchronization block or synchronization method.
-
You can wake up a dormant thread by calling the interrupt() method.
-
sleep is a static method that can be called anywhere
Compared with the sleep method, sleep is a static method, and the thread of sleep is not a lock resource, while the join method is an object method, and the object lock will be released during the waiting process;
These are the above. If you want to know more about the interview class, please pay attention!