Thread foundation of concurrent programming

  1. Thread foundation of concurrent programming
    1. 1. Understanding threads
    2. 2. Creation and running of threads
      1. 2.1 inherit Thread class and override run method
      2. 2.2 implement the Runable interface and rewrite the run method
      3. 2.3 using FutureTask
    3. 3. Thread notification and waiting
      1. 3.1 wait() function
        1. Practical simple producers and consumers
      2. 3.2 wait(long timeout) function
      3. 3.3 wait(long timeout, int nanos) method
      4. 3.4 notify() function
      5. 3.5 notifyAll() function
    4. 4. Wait for the thread to execute the terminated join method
    5. 5. Sleep method for thread sleep
    6. 6. yield method of giving up CPU execution right
    7. 7. Thread interrupt
    8. 8. Understand context switching
    9. 9 thread deadlock
    10. 10. Daemon thread and user thread
    11. 11. ThreadLocal
      1. 1. ThreadLocal introduction
      2. 2. Implementation principle of ThreadLocal
        1. set method
        2. get method
        3. remove method
      3. 3. ThreadLocal does not support inheritance
      4. 4. inheritableThreadLocal class

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

  1. This shared variable is used as a parameter when executing synchronized synchronization code
synchronized(sharedVaribale){
    //doSomething
}
  1. 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.

  1. 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.
  2. 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);
}
  1. 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:

  1. The current thread has run out of CPU time slices and is in a ready state
  2. 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)

  1. 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
  2. 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
  3. 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
  4. 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.

Keywords: Java Multithreading

Added by PTS on Wed, 15 Sep 2021 05:52:36 +0300