- Thread foundation of concurrent programming
- 1. Understanding threads
- 2. Creation and running of threads
- 3. Thread notification and waiting
- 4. Wait for the thread to execute the terminated join method
- 5. Sleep method for thread sleep
- 6. yield method of giving up CPU execution right
- 7. Thread interrupt
- 8. Understand context switching
- 9 thread deadlock
- 10. Daemon thread and user thread
- 11. ThreadLocal
Thread foundation of concurrent programming
1. Understanding threads
2. Creation and running of threads
2.1 inherit Thread class and override run method
public static class MyThread extends Thread{ @Override public void run(){ sout('I am a child thread which extends Thread class'); } } public static void main(String[] args) { //Create thread MyThread myThread=new MyThread(); //Start thread myThread.start(); }
2.2 implement the Runable interface and rewrite the run method
public static class RunableTask implements Runable{ @Override public void run(){ sout("I am a child thread which implements Runable interface") } } public static void main(String[] args) { //Create thread Thread thread=new Thread(new RunableTask()); //Start thread thread.start(); }
2.3 using FutureTask
//Create a task class, similar to the Runable interface public static class CallerTask implements Callable<String>{ @Override public String call() throw Exception{ return "hello wolrd"; } } public static void main(String[] args) { //Create asynchronous task FutureTask<String> futureTask=new FutureTask<>(new CallerTask()); //Create a thread to perform asynchronous tasks and start the thread Thread thread=new Thread(futureTask); thread.start(); try{ //Wait until the task is completed and the result is returned String result=futureTask.get(); sout(result); }catch(ExecutionException e | InterruptedException e){ e.printStackTrace(); } }
3. Thread notification and waiting
3.1 wait() function
When a thread calls the wait() method of a shared variable, the calling thread will be blocked and suspended until one of the following things happens
- Another thread called the notify() method or notifyAll() method of the shared object
- Other threads call the thread's interrupt() method, and the thread throws an InterruptedException exception and returns
If the thread calling the wait() method does not obtain the monitor lock of the shared object in advance, the calling thread will throw an IllegalMonitorStateException when calling the wait() method
There are two ways for a thread to obtain the monitor lock of a shared variable
- This shared variable is used as a parameter when executing synchronized synchronization code
synchronized(sharedVaribale){ //doSomething }
- The method of the shared variable is called, and the method uses the synchronized modifier
synchronized void add(int a ,int b){ //doSomething }
Pay Attention a thread can change from a suspended state to a runnable state (i.e. wake up), even if the thread is not notified by other threads calling notify() and notifyAll() methods, or is interrupted or waits for timeout, which is the so-called false wake-up.
Nip in the bud is seldom applied in practice, but it is necessary to prevent the awakening of the thread from being in the bud. The practice is to test whether the thread is waken up or not, then continue to wait, that is to say, call wait() method in a cycle to prevent it. The condition for exiting the loop is that the condition for waking up the thread is met
synchronized(obj){ while( invoked condition is not satified){ obj.wait(); } }
Practical simple producers and consumers
Where queue is a shared variable. Before calling the wait() method of queue, the producer thread uses the synchronized keyword to get the monitor lock of queue, so calling the wait() method will not throw an exception, IllegalMonitorStateException.
If the current queue has no free capacity, the wait() method of the queue will be called to suspend the current thread. Loops are used here to avoid false wakeups. If the current thread is falsely awakened, but there is still no free capacity in the queue, the current thread still calls back and hangs itself with the wait() method
public class ProducerAndConsumer{ private static final Queue<Long> queue=new ArrayDeque<>(); private static final int MAX_SIZE=8; public static void main(String[] args) { //The lambda expression of the production thread directly creates an anonymous class that implements the run method of the Runnable interface Thread producerThread=new Thread(()->{ //Get shared variable monitor lock synchronized(queue){ //If the consumption queue Iman does not meet the wakeup conditions, continue to wait while(queue.size()==MAX_SIZE){ //Suspend the current thread and release the shared variable monitor lock so that the consumer thread can obtain the lock and consume goods try{ queue.wait(); }catch(InterruptedException ex){ ex.printStackTrace(); } } //Produce items and notify the wake-up consumer thread Long seed=Math.round(Math.random()*10+1); queue.offer(seed); sout(seed); queue.notifyAll(); } }); //Consumer thread Thread consumerThread=new Thread(()->{ //Get shared variable monitor lock synchronized(queue){ //If the consumption queue is empty and the wakeup condition is not met, continue to wait while(queue.size()==0){ //Suspend the current thread and release the shared variable monitor lock so that the producer thread can obtain the lock to produce items try{ queue.wait(); }catch(InterruptedException ex){ ex.printStackTrace(); } } //Consume items and notify the wake-up producer thread Long seed=queue.poll(); sout(seed); queue.notifyAll(); } }); producerThread.start(); consumerThread.start(); } }
After thread A obtains the lock on the queue, all subsequent threads attempting to produce items and threads consuming items will be blocked and suspended at the place where the monitor lock is obtained. When thread A finds that the current queue is full after acquiring the lock, it will call the queue.wait() method to block itself, and then release the acquired monitor lock.
If the lock is not released, other producers and consumers have been blocked and suspended, and thread A has also been suspended, resulting in A deadlock state. Here, thread A suspends itself and releases the lock on the shared variable in order to break the hold and wait principle, one of the necessary conditions for deadlock
After calling the wait() method of the contributing variable, the current thread will only release the locks on the current variable. If the current variable also holds locks on other shared variables, these locks will not be released.
public class ReleaseLockTest{ private static Object rA=new Object(); private static Object rB=new Object(); public static void main(String[] args) { Thread tA=new Thread(()->{ try{ synchronized(rA){ sout("tA gets rA lock"); synchronized(rB){ sout("tA gets rB lock"); sout("tA release rA lock") rA.wait(); } } }catch(InterruptedException ex){ ex.printStackTrace(); } }); Thread tB=new Thread(()->{ try{ Thread.sleep(1000); //Sleep for 1s synchronized(rA){ sout("tB gets rA lock"); synchronized(rB){ sout("tB gets rB lock"); sout("tB release rA lock") rA.wait(); } } }catch(InterruptedException ex){ ex.printStackTrace(); } }); //Start thread tA.start(); tB.start(); //Wait for all threads to end tA.join(); tB.join(); sout("main over!") } }
Output results:
tA gets rA lock tA gets rB lock tA releases rA lock tA gets rA lock
Analyzing the output results, we can see that the tB thread is blocked to the position where the shared variable rB monitor lock is obtained. From this, we can see that calling rA.wait() method by thread tA will only release the rA monitor lock, while the rB monitor lock is still occupied by thread tA and not released.
When a thread calls the wait method of a shared object and is blocked and suspended, if another thread interrupts the thread, the thread will throw an InterruptedException exception and return.
public class waitNotifyInterrupt{ static Object obj=new Object(); public static void main(String[] args) { Thread thread=new Thread(()->{ try{ sout("---begin--------"); synchronized(obj){ obj.wait(); } }catch(InterruptedException ex){ ex.printStackTrace(); } }); thread.start(); Thread.sleep(1000); sout("----------begin interrupt thread ----------"); thread.interrupt(); sout("--------end interrupt thread ---------"); } }
3.2 wait(long timeout) function
This method has one more timeout parameter than wait(). The difference is that if a thread suspends calling the method of the shared object and is not awakened by other threads calling the notify() or notifyAll() method of the shared variable within the specified timeout ms time, the function will still return due to timeout. If timeout is set to 0, the effect is the same as that of the wait method, because wait(0) is called inside the wait method. It should be noted that if a negative timeout is passed when calling this method, an IllegalArgumentException will be thrown.
3.3 wait(long timeout, int nanos) method
The wait(timeout) function is called internally. The following code increments the parameter timeout by 1 only when nanos > 0
public final void wait(long timeout,int nanos) throws InterruptedException{ if(timeout<0){ throw new IllegalArgumentException("timeout value is negative"); } if(nanos<0 || nanos>999999){ throw new IllegalArgumentException("nanos timeout value out of range"); } if(nanos>0){ timeout++; } wait(timeout); }
3.4 notify() function
After a thread calls the notify() function of the shared object, it will wake up a thread suspended after calling the wait series methods on the shared variable. There may be multiple threads waiting on a shared variable. The specific wake-up of the waiting thread is random.
In addition, the awakened thread cannot immediately return from the wait method and continue to execute. It can only return after obtaining the monitor lock of the shared object. That is, after the awakened thread releases the monitor lock on the shared variable, the awakened thread will not necessarily obtain the monitor lock on the shared object, but also compete with other threads for the lock.
Similar to the wait series methods, only after the current thread obtains the monitor lock of the shared variable can it call notify()fangfa of the shared variable, otherwise an IllegalMonitorStateException will be thrown.
3.5 notifyAll() function
Wake up all threads suspended due to calling wait series functions on the shared variable.
It should be noted that the diaoynotifyAll method on the shared variable will only wake up the threads blocked on the contributing variable before calling this method. If a thread is blocked by calling the shared variable wait after calling the notifyAll method, the thread will not wake up.
4. Wait for the thread to execute the terminated join method
The wait notification method described earlier is a method in the Object class, while the join method is directly provided by the Thread class. The join method is a method without parameters and the return value is void.
In addition, thread A will be blocked after calling the join method of thread B. when other threads call the interrupt method of thread A and interrupt thread A, thread A throws an InterruptedException exception and returns.
public static void main(String[] args) { Thread t1=new Thread(()->{ sout("t1 begin run !"); for(;;){ } }); //Get main thread Thread mainThread=Thread.currentThread(); Thread t2=new Thread(()->{ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } //Interrupt main thread mainThread.interrupt(); }); //Start child thread t1.start(); //Delay 1s to start thread t2.start(); try{ t1.join(); }catch(InterruptedException e){ sout("main thread"+e) } }
5. Sleep method for thread sleep
- When an executing thread invokes the static method sleep in the Thread class, the calling thread temporarily gives the right to execute the specified time, that is, the CPU does not participate in the scheduling.
- However, the monitor resources owned by the thread, such as locks, are still held
- When the specified sleep time is up, the function will return normally, the thread is in the ready state, and then participate in CPU scheduling. After obtaining CPU resources, it can continue to run
- If other threads call the interrupt method of the thread during sleep and interrupt the thread, the thread will throw an InterruptedException exception at the place where the sleep method is called and return
public class SleepTest{ //Create an exclusive lock private static final Lock lock=new ReetrantLock(); public static void main(String[] args) throws InterruptedException{ Thread tA=new Thread(()->{ //Get exclusive lock lock.lock(); try{ sout("child thread A is sleeping"); Thread.sleep(10000); sout("child thread A is in awaked"); }catch(InterruptedException ex){ ex.printStackTrace(); }finally{ //Release lock lock.unlock(); } }); Thread tB=new Thread(()->{ //Get exclusive lock lock.lock(); try{ sout("child thread B is sleeping"); Thread.sleep(10000); sout("child thread B is in awaked"); }catch(InterruptedException ex){ ex.printStackTrace(); }finally{ //Release lock lock.unlock(); } }); tA.start(); tB.start(); } }
The output results are as follows
child thread A is sleeping child thread A is in awaked child thread B is sleeping child thread B is in awaked
6. yield method of giving up CPU execution right
When a Thread calls the Thread's static method yield, it actually implies that the Thread scheduler requests to give up its CPU, but the Thread scheduler can unconditionally ignore this hint.
When a thread calls the yield method, the current thread will give up the CPU usage right and then be in the ready state. The thread scheduler will obtain the thread with the highest thread priority from the thread ready state. Of course, it may also schedule to the thread that just gave up the CPU to obtain the CPU execution right.
Differences between Sleep and yield methods:
- When a thread calls the sleep method, the calling thread will be blocked and suspended for a specified time. During this period, the thread scheduler will not schedule the thread
- When the yield method is called, the thread only gives up its remaining time slice and is not blocked or suspended. Instead, it is in a ready state. The thread scheduler may schedule to the current thread for execution next time
7. Thread interrupt
Java thread interrupt is an inter thread cooperation mode. Setting the interrupt flag of a thread does not directly terminate the execution of the thread, but the interrupted thread handles it by itself according to the interrupt state.
- void interrupt() method: interrupt the thread. When thread A is running, thread B can call the interrupt() method of thread A to set the interrupt flag of thread A to true and return it immediately. Setting the flag is just setting the flag. Thread A is not actually interrupted, and it will continue to execute. If thread A is blocked and suspended because it calls wait, sleep and join methods, then if thread B calls the interrupt method of thread A, thread A will throw an exception InterruptedException and return when calling these methods.
- boolean isInterrupted() method: check whether the current thread is interrupted. If yes, return true; otherwise, return false
public boolean isInterrupted(){ //Pass false to indicate that the interrupt flag is not cleared return isInterrupted(false); }
- boolean interrupted() method: check whether the current Thread is interrupted. If yes, return true; otherwise, return false. Different from isInterrupted() method, this method clears the interrupt flag if it finds that the current Thread is interrupted, and this method is a static method, which can be called directly through Thread. Inside the interrupted () method is to get the interrupt flag of the current calling Thread instead of calling the interrupt flag of the instance object of the interrupted () method.
public static boolean interrupted(){ //Clear interrupt flag return currentThread().isInterrupted(true); }
A classic example of a thread using Interrupted to exit gracefully:
public void run{ try{ ... //Thread exit condition while(!Thread.currentThread().isInterrupted() && more work to do){ //do more work } }catch(InterruptedException ex){ //thread was interrupted during sleep or wait }finally{ //cleanup,if required } }
8. Understand context switching
The CPU resource allocation adopts the strategy of time slice rotation, which allocates a time slice to each thread, and the thread occupies the CPU to execute tasks in the time slice. After the current thread uses the time slice, it will be in the ready state and give up the CPU for other threads. This is context switching.
When switching the thread context, save the execution site of the current thread. When executing again, reply to the execution site according to the saved execution site information to continue execution.
Timing of thread context switching:
- The current thread has run out of CPU time slices and is in a ready state
- When the current thread is interrupted by another thread
9 thread deadlock
Deadlock refers to the phenomenon that two or more threads wait for each other due to competing for resources during execution.
Prerequisites for deadlock generation (four)
- Mutually exclusive condition: refers to the exclusive use of the acquired resource by a thread, that is, the resource can only be occupied by one thread at the same time. If there are other threads requesting the resource at this time, the requester can only wait until the thread occupying the resource is released
- Request and hold: it means that a thread has held at least one resource, but puts forward a new resource request, and the new resource has been occupied by other threads, so the current thread will be blocked, but blocking does not release the resources it already holds
- Inalienable condition: the resource obtained by a thread cannot be preempted by other threads before it is used up. The resource can only be released by itself after it is used up
- Loop waiting condition: when a deadlock occurs, there must be a thread - a ring chain of resources
Deadlock avoidance only needs to destroy at least one necessary condition for constructing a deadlock. At present, only request and hold and loop waiting conditions can be destroyed.
The cause of deadlock is closely related to the order of resource application. Deadlock can be avoided by using the order principle of resource application.
/** * @Author: gcc * @CreateTime: 2021-09-13 14:41 * @Description: Deadlock example (deadlock can be solved by adjusting the order consistency of threads A and B requesting resources resourceA and resourceB) */ public class DeadLockTest { private static final Object resourceA=new Object(); private static final Object resourceB=new Object(); public static void main(String[] args) throws InterruptedException { Thread tA=new Thread(()->{ synchronized(resourceA){ Thread self=Thread.currentThread(); System.out.println(String.format("%s gets resourceA",self)); try{ //Sleep for 1s to allow cpu execution time Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(String.format("%s waiting resourceB",self)); synchronized(resourceB){ System.out.println(String.format("%s gets resourceB",self)); } } }); // ① Deadlock generation Thread tB=new Thread(()->{ synchronized(resourceB){ Thread self=Thread.currentThread(); System.out.println(String.format("%s gets resourceB",self)); try{ //Sleep for 1s to allow cpu execution time Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(String.format("%s waiting resourceA",self)); synchronized(resourceA){ System.out.println(String.format("%s gets resourceA",self)); } } }); // ② Resolve deadlock // Thread tB=new Thread(()->{ // synchronized(resourceA){ // Thread self=Thread.currentThread(); // System.out.println(String.format("%s gets resourceA",self)); // try{ // //Sleep for 1s to allow cpu execution time // Thread.sleep(1000); // }catch(InterruptedException e){ // e.printStackTrace(); // } // System.out.println(String.format("%s waiting resourceB",self)); // synchronized(resourceB){ // System.out.println(String.format("%s gets resourceB",self)); // } // } // }); tA.start(); tB.start(); tA.join(); tB.join(); System.out.println("main is over"); } } Output: corresponding① The program causing the deadlock cannot end Thread[Thread-0,5,main] gets resourceA Thread[Thread-1,5,main] gets resourceB Thread[Thread-0,5,main] waiting resourceB Thread[Thread-1,5,main] waiting resourceA corresponding② The deadlock is solved, and the normal operation of the program ends Thread[Thread-0,5,main] gets resourceA Thread[Thread-0,5,main] waiting resourceB Thread[Thread-0,5,main] gets resourceB Thread[Thread-1,5,main] gets resourceA Thread[Thread-1,5,main] waiting resourceB Thread[Thread-1,5,main] gets resourceB main is over
10. Daemon thread and user thread
There are two types of threads in Java: daemon thread and user thread. When the JVM starts, the main function is called. The thread where the main function is located is a user thread (daemon thread, such as garbage collection thread).
Difference between daemon thread and user thread: when the last user thread ends, the JVM will exit normally, regardless of whether there is currently a daemon thread, that is, whether the daemon thread ends or not does not affect the JVM exit.
As long as a user thread does not end, the JVM will not exit under normal circumstances.
/** * @Author: gcc * @CreateTime: 2021-09-13 15:28 * @Description: Daemon and user thread examples */ public class daemonTest { public static void main(String[] args) { Thread tA=new Thread(()->{ for (;;){ } }); //Set as the daemon thread, and the JVM and other main threads will exit normally as soon as they are finished; Otherwise, the JVM cannot end because the user sub thread executes in an infinite loop tA.setDaemon(true); //Start child thread tA.start(); System.out.println("main is over"); } }
11. ThreadLocal
1. ThreadLocal introduction
Multiple threads accessing the same variable are particularly prone to concurrency problems, especially when multiple threads need to write to a shared variable. In order to ensure thread safety, general users need to synchronize properly when accessing shared variables.
The synchronization measure is locking. ThreadLocal class can ensure that when a variable is created, each thread accesses its own thread variable.
ThreadLocal is provided by jdk. It provides thread local variables. When you create a ThreadLocal variable, each thread accessing this variable will have a local copy of this variable. When multiple threads operate on this variable, the actual operation is
It is a variable in its own local memory, so as to avoid thread safety problems.
/** * @Author: gcc * @CreateTime: 2021-09-13 15:35 * @Description: ThreadLocalTest Example */ public class ThreadLocalTest { private static final ThreadLocal<String> localVariable=new ThreadLocal<>(); public static void main(String[] args) { Thread tA=new Thread(()->{ localVariable.set("tA local variable"); print(); System.out.println(String.format("%s remove after:%s",Thread.currentThread(),localVariable.get())); }); Thread tB=new Thread(()->{ localVariable.set("tB local variable"); print(); System.out.println(String.format("%s remove after:%s",Thread.currentThread(),localVariable.get())); }); tA.start(); tB.start(); } private static void print() { //Prints the variable value in the local memory of the current thread System.out.println(String.format("%s:%s",Thread.currentThread(),localVariable.get())); //Then clear the value in the local memory of the current thread // localVariable.remove(); } } Output: Thread[Thread-1,5,main]:tB local variable Thread[Thread-0,5,main]:tA local variable Thread[Thread-1,5,main] remove after:tB local variable Thread[Thread-0,5,main] remove after:tA local variable If will print Methods in vivo remove Statement, and the output result is as follows Thread[Thread-1,5,main]:tB local variable Thread[Thread-0,5,main]:tA local variable Thread[Thread-1,5,main] null Thread[Thread-0,5,main] null
2. Implementation principle of ThreadLocal
The Thread class has a threadLocals and an inheritableTreadLocals, both of which are objects of type ThreadLocal.ThreadLocalMap, which is a customized HashMap. By default, these two variables are null for each Thread. They are created only when the Thread's run method calls the set or get method of ThreadLocal for the first time.
In fact, the local variables of each Thread are not stored in the ThreadLocal instance, but in the threadlocals variable of the calling Thread, that is, the local variables of ThreadLocal type are stored in the specific Thread memory space. ThreadLocal is a tool shell. The value value is stored in the threadlocals of the calling Thread through the set method. When the calling Thread calls its get method, it is used from the threadlocals variable of the current Thread. Threadlocales in Thread is designed as a map structure because each Thread can be associated with multiple ThreadLocal variables.
Simply analyze the implementation of get, set and remove methods of ThreadLocal
set method
public void set(T value){ //(1) Get current thread Thread t=Thread.currentThread(); //(2) Take the current thread as the key to find the corresponding thread variable, and set it if found ThreadLocalMap map=getMap(t); if(map != null){ map.set(this,value); }else{ //(3) The first call creates the HashMap corresponding to the current thread createMap(t,value); } } ThreadLocalMap getMap(Thread t){ return t.threadLocals; } //Create the threadLocals member variable of the current thread and initialize the bound thread local variable value firstValue void createMap(Thread t,T firstValue){ t.threadLocals=new ThreadLocalMap(this,firstValue); } //ThreadLocal.ThreadLocalMap class static class ThreadLocalMap{ private static final int INITIAL_CAPACITY=16; private Entry[] table; private int size=0; private int threshold; //default to 0 private void setThreshold(int len){ threshold=len*2/3; } //Constructor ThreadLocalMap(ThreadLocal<?> firstKey,Object firstValue){ table=new Entry[INITIAL_CAPACITY]; int i=firstKey.threadLocalHashCode & (INITIAL_CAPACITY-1); table[i]=new Entry(firstKey,firstValue); size=1; setThreshold(INITIAL_CAPACITY); } //len modular operation private static int nextIndex(int i, int len){ return ((i+1<len)?i+1:0) } //len modular operation private static int prevIndex(int i,int len){ return ((i-1>=0)?i-1:len-1); } /** * Get the entry associated with key. The method itself handles only the fast path:a direct hit of existing key * It otherwise relays to getEntryAfterMiss. This is designed to maximize performance for direct hits,in part by * marking this method readily inlineable * @param key the thread local object * @return the entry assocaited with the key,or null if no such * */ private Entry getEntry(ThreadLocal<?> key){ int i=key.threadLocalHashCode & (table.length-1); Entry e=table[i]; if(e!=null && e.get()==key){ return e; }else{ return getEntryAfterMiss(key ,i , e); } } /** * Version of getEntry method for use when key is not found in its direct hash slot * @param key the thread local object * @param i the table index for key's hash code * @e the entry at table[i] * @return the entry associated with key,or null if no such * */ private Entry getEntryAfterMiss(ThreadLocal<?> key,int i,Entry e){ Entry[] tab=table; int len=tab.length; while(e!=null){ ThreadLocal<?> k=e.get(); if(k==key) return e; if(k==null) expungeStaleEntry(i); else i=nextIndex(i,len); e=tab[i]; } return null; } //set method private void set(ThreadLocal<?> key ,Object value){ Entry[] tab=table; int len=tab.length; int i=key.threadLocalHashCode & (len -1); for(Entry e=tab[i] ; e!=null ; e=tab[i=nextIndex(i,len)]){ ThreadLocal<?> k=e.get(); if(k==key){ e.value=value; return ; } if(k==null){ replaceStaleEntry(key,value,i); return ; } } tab[i]=new Entry(key,value); int sz=++size; if(!cleanSomeSlots(i,sz) && sz >=threshold){ rehash(); } } } //ThreadLocal.ThreadLocalMap.Entry class static class Entry extends WeakReference<ThreadLocal<?>>{ //value bound to ThreadLocal Object value; Entry(ThreadLocal<?> k,Object v){ super(k); value=v; } }
As can be seen from the above code, the function of getMap(t) is to obtain the thread's own variable threadLocals, which is bound to the thread's member variable.
- If the return value of getMap(t) is not empty, set the value value to threadLocals, that is, put the current variable value into the memory variable threadLocals of the current thread. threadLocals is a HashMap structure, where key is the reference of the instance object of the current ThreadLocal, and value is the value passed through the set method.
- If the map is empty, it means that the set method is called for the first time. At this time, the threadLocals variable of the current thread is created
get method
public T get(){ //(4) Get current thread Thread t=Thread.currentThread(); //(5) Take the current thread as the key to find the corresponding thread variable, and set it if found ThreadLocalMap map=getMap(t); //(6) If threadLocals is not empty, return the value of the corresponding local variable if(map!=null){ ThreadLocalMap.Entry e=map.getEntry(this); //this is the instance object reference of the current ThreadLocal if(e!=null){ /unchecked/ T result=(T)e.value; return result; } } //If threadLocals is null, the threadLocals member variable of the current thread will be initialized return setInitialValue(); } private T setInitialValue(){ //Initialize to null T value=initialValue(); Thread t=Thread.currentThread(); ThreadLocalMap map=getMap(t); if(map!=null){ map.set(this,value); //this is the instance object reference of the current ThreadLocal }else{ createMap(t,value); } if(this instanceof TerminatingThreadLocal){ TerminatingThreadLocal.register((TerminatingThreadLocal)this); } return value; } protected T initialValue(){ return null; }
From the above set and get methods, we can see that if the calling thread first invites any one of the two methods, it initializes the member variable threadLocals of the current thread. Additional, when calling the get method for the first time (before calling the set method), it will assign null to the local variable referred to in this (that is, the instance object reference of the current ThreadLocal).
remove method
public void remove(){ ThreadLocalMap map=getMap(Thread.currentThread()); if(map!=null){ map.remove(this); //this is the instance object reference of the current ThreadLocal } } private void remove(ThreadLocal<?> key){ Entry[] tab=table; int len=tab.length; int i=key.threadLocalHashCode &(len-1); for(Entry e=tab[i];e!=null;e=tab[i=nextIndex(i,len)]){ if(e.get()==key){ e.clear(); expungeStaleEntry(i); return ; } } }
Summary: there is a member variable named threadLocals inside each thread. The type of the variable is HashMap, where key is the this reference of the ThreadLocal variable we defined, and value is the value we set using the set method. The local variables of each thread are stored in the thread's own memory variable threadLocals. If the current thread does not die, these local variables will always exist, which may cause memory overflow. Therefore, remember to remove the local variables in the threadLocals of the corresponding thread after use.
3. ThreadLocal does not support inheritance
When the same ThreadLocal variable is set in the parent thread, it is not available in the child thread, because when the get method is invoked in the child thread, the current thread is the thread thread instead of the mian thread, and here the set method sets the thread variable to be the main thread, the two are different threads, so they can not access each other. There is a way for the child thread to access the value * * inheritableThreadLocals in the parent thread**
public class ThreadLocalTest{ private static final ThreadLocal<String> local=new ThreadLocal<String>(); public static void main(String[] args) { Thread tA=new Thread(()->{ System.out.println("child:"+local.get()); }); tA.start(); local.set("hello world!"); sout("main:"+local.get()); } } //The output results are as follows main: hello world child: null
4. inheritableThreadLocal class
public class InheritableThreadLocal<T> extends ThreadLocal<T>{ //(1) protected T childValue(T parentValue){ return parentvalue; } //(2) ThreadLocalMap getMap(Thread t){ return t.InheritableThreadLocals; } //(3) void createMap(Thread t,T firstValue){ t.InheritableThreadLocals=new ThreadLocalMap(this,firstValue); } }
- inheritableThreadLocal inherits ThreadLocal and overrides three methods
- It can be seen from code (3) that the createMap method is restarted. When the set method is called for the first time, an instance of the inheritableThreadLocals variable of the current thread is created, which is no longer threadLocals
- Code (2) shows that when getMap is called to obtain the map variable inside the current thread, inheritablethreadlocales is obtained, and threadlocales is no longer obtained
- To sum up, in the world of inheritablethreadlocals, the variable inheritablethreadlocals replaces threadLocals
When does the rewritten code (1) execute? How can a child Thread access the parent Thread's local variables? You can see the clue from the constructor of Thread.
private Thread(ThreadGroup g,Runnable target,String name,long stackeSize, AccessControlContext acc,boolean inheritThreadLocals){ //The inheritThreadLocals variable defaults to true ... //(4) Get current thread Thread parent = currentThread(); ... //(5) If the inheritableThreadLocals variable of the parent thread is not null if(inheritThreadLocals && parent.InheritableThreadLocals!=null){ //(6) Set the inheritableThreadLocals variable in the child thread this.InheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } ... } static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap){ return new ThreadLocalMap(parentMap); } /** * ThreadLocalMap Constructor for * Construct a new map including all Inheritable ThreadLocals from given parent map. * Called only by createInheritedMap */ private ThreadLocalMap(ThreadLocalMap parentMap){ Entry[] parentTable=parentMap.table; int len=parentTable.length; setThreshold(len); table=new Entry[len]; for(int j=0;j<len;++j){ Entry e=parentMap[j]; if(e!=null){ /unchecked/ ThreadLocal<Object> key=(ThreadLocal<Object>)e.get(); if(key!=null){ //(7) Call overridden method Object value=key.childValue(e.value); Entry c=new Entry(key,value); int h=key.threadLocalHashCode & (len-1); while(table[h]!=null){ h=nextIndex(h,len); } table[h]=c; size++; } } } }
When creating a thread, code (4) obtains the current thread (here refers to the thread where the main function is located, that is, the parent thread), and then code (5) determines whether the inheritableThreadLocals property in the thread where the main function is located is null. The inheritableThreadLocals variable is operated in combination with the get and set methods of the InheritableThreadLocal class mentioned above. Therefore, the inheritableThreadLocals here is not null, so execute the code (6). You can see that in createInheritedMap, the inheritableThreadLocals variable of the parent thread is used as the constructor to create a new ThreadLocalMap variable, and then assigned to the inheritableThreadLocals variable of the child thread.
Summary: the InheritableThreadLocal class saves the local variable to the inheritableThreadLocals variable of the specific thread by overriding (2) and (3). When the thread sets the variable through the set or get method of the InheritableThreadLocal class instance, it will create the inheritableThreadLocals variable of the current thread.
At the same time, when the parent Thread creates a child Thread, the Thread constructor will copy a copy of the local variable in the inheritableThreadLocals variable in the parent Thread and save it to the inheritableThreadLocals variable of the child Thread.
public class InheritableThreadLocalTest{ private static final ThreadLocal<String> local=new InheritableThreadLocal<>(); private static final ThreadLocal<String> local2=new InheritableThreadLocal<>(); public static void main(String[] args) { local.set("local"); Thread thread=new Thread(()->{ String str1=local.get(); sout(str1) String str2=local2.get(); sout(str2); }); local2.set("local2"); local.set("local copy"); thread.start(); sout("main:"+local.get()); sout("main:"+local2.get()); } } //Output results main:local copy main:local2 local null
You can see this example usage. Only the InheritableThreadLocal class instance object value of the parent thread before creating the child thread is passed to the child thread, and any changes after creating the child thread statement will not be updated to the child thread. The reason is as shown in the above source code analysis: the inheritableThreadLocals variable of the parent thread will be copied during the execution of the constructor Thread() of the child thread, After the constructor is executed, the operations of the parent thread on the InheritableThreadLocal class instance object will not be reflected in the child thread
Usage scenario:
- The child thread needs to use the user information stored in the threadlocal variable
- Some middleware need to record the whole calling link of unified id tracking
In fact, child threads use many threadlocal methods in the parent thread. For example, when creating a thread, the variables in the parent thread are passed in and copied to the child thread; Construct a map in the parent thread and pass it to the child thread as a parameter.