Explore Java Concurrency-ThreadLocal

Explore Java Concurrency-ThreadLocal

Using ThreadLocal can maintain thread closure, associate a value in the thread with the object that saves the value, and prevent the sharing of variable singleton variables or global variables, but improper use can also cause memory leaks. Learn about it first, and then use it.

Speaking from SimpleDateFormat

SimpleDateFormat is our commonly used date formatting tool, but friends who are familiar with it all know that it is thread-unsafe.

SimpleDateFormat usage

public class Acuptest {

    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
        System.out.println(sdf.format(new Date()));
    }
}

SimpleDateFormat thread unsafe scenario

There is no problem with the above usage at all, but now spring is everywhere. Many classes exist in the spring container in the form of beans and are shared by various kinds. If they are not careful, they will be written like the following.

public class Acuptest {

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");

    public String format(Date date) {
        return sdf.format(date);
    }
}

It's just that I don't see any problems, but since it is mentioned that SimpleDateFormat is thread-unsafe, let's see why it is unsafe.

SimpleDateFormat thread unsafe analysis

Enter the source code and only look at the key parts.

public abstract class DateFormat extends Format {

   //a member variable
    protected Calendar calendar;

   //an abstract method
    public abstract StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition);

   //Provide methods for external use
    public final String format(Date date){
        return format(date, new StringBuffer(),
                      DontCareFieldPosition.INSTANCE).toString();
    }
}

public class SimpleDateFormat extends DateFormat {

   //Implemented the abstract method of the parent class
    @Override
    public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos){
        pos.beginIndex = pos.endIndex = 0;
        return format(date, toAppendTo, pos.getFieldDelegate());
    }

    private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
       //The problem can be found here, and the member variable is set as a parameter passed in
       //In the case of concurrency, the value of the calendar is not credible. Maybe thread A has just set the front foot and is ready to execute the next statement, and thread B will change the value immediately afterward.
       //Convert input date to time field list
        calendar.setTime(date);

        boolean useDateFormatSymbols = useDateFormatSymbols();

       //slightly
    }
}

SimpleDateFormat thread safe usage

Use local variables

As long as you don't let multiple threads access the same object, you can use a new object every time you want to use it.

Use ThreadLocal

In many cases, certain objects are not suitable for frequent creation and destruction, but they are not thread safe like SimpleDateFormat. At this time ThreadLocal is useful.

public class Acuptest {

   //Assign a SimpleDateFormat to each thread separately, which can be reused within threads, but cannot be shared between threads.
    private ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
           //When the get() method cannot get the SimpleDateFormat object of the current thread, it will call this method to create one and bind it to the thread
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS");
        }
    };

    public String format(Date date) {
        return sdf.get().format(date);
    }

ThreadLocal source code analysis

public class ThreadLocal<T> {
   //...
   //Get the object bound by the current thread, if not, it will call initialValue to generate one and bind it
    public T get() {
       //Get the current thread
        Thread t = Thread.currentThread();
       //Get a MAP from the current thread
       //key: ThreadLocal
       //value: generic type of ThreadLocal<T>
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
       //The Thread object may not have created the ThreadLocalMap member variable
       //Or there is no <T> value corresponding to the current ThreadLocal object in ThreadLocalMap
       //At this time, the initial value needs to be set
        return setInitialValue();
    }

   //Get the MAP in the thread
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

   //set initial value
    private T setInitialValue() {
       //Create a new object
        T value = initialValue();
       //Reacquire the current thread, because there is no parameter to receive thread information
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);//set the initial value
        else
            createMap(t, value);//Create MAP and set initial value
        return value;
    }

   //Initialize an object, return null by default, this method can be overridden when in use
    protected T initialValue() {
        return null;
    }
   //...
}

Thread source code analysis

In the above source code, you can see that ThreadLocal uses the member variable threadLocals in Thread many times, so I have a simple understanding of the structure of the Thread object.

public class Thread implements Runnable {

   /* ThreadLocal values ​​pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

   /*
     * InheritableThreadLocal values ​​pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

   //slightly
}

public class ThreadLocal<T> {

    static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
           /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

       /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
    }
}

threadLocals and inheritableThreadLocals

From the Thread source code, you can see that there are two member variables of type ThreadLocal.ThreadLocalMa. One is inheritableThreadLocals, which has not been seen before. This variable is not for ThreadLocal, but for another similar tool, InheritableThreadLocal.

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

From the source code point of view, InheritableThreadLocal inherits ThreadLocal, and then the MAP used is changed, and the others are nothing special.

But InheritableThreadLocal has a special function: it can use the inheritableThreadLocals variable of the parent thread to realize the parent-child thread shared variable.

Why InheritableThreadLocal can let the child thread use the parent thread's variables, the key is not in it, but in the initialization process of the Thread class, when Thread is initialized,

public class Thread implements Runnable {

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
       //slightly
        Thread parent = currentThread();
       //slightly
       //inheritThreadLocals is true by default
       //If the parent thread inheritableThreadLocals is not empty, make a copy
       //Value copy, non-reference copy
       //just copy the object currently owned by the parent thread
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
       //slightly
    }
}

Weak references in ThreadLocal (WeakReference)

Notice from the above source code: ThreadLocal.ThreadLocalMap.Entry extends WeakReference<threadlocal>/threadlocal

The key of Entry is ThreadLocal, which is a weak reference (reclaimed when it is scanned by GC). If this is not the case, when the ThreadLocal is used up, but the thread has not yet ended, so the Thread still holds a strong reference to the ThreadLocal, then it will never be recycled, and it can be considered a memory leak.

ThreadLocal memory leak

Even if weak references are used, there is still the possibility of memory leaks. Because the weak reference is only the entry key (ThreadLocal), the value (generic T) is not a weak reference. The final possible result is that the ThreadLocal is recycled, and the KEY in the MAP in the Thread is gone, but the value is still there. In this way, the value will never be returned by the get() method, and it does exist in the memory and does not want to dissipate. .

The internal implementation tries to avoid memory leaks:

When the get(), set(), and remove() methods of ThreadLocal are called, the Value whose Key is null in all the entries in the thread ThreadLocalMap will be cleared, and the entire Entry will be set to null, which is conducive to the next memory recovery.

If these methods are not called to trigger the process, memory leaks will still occur, so after the thread runs out of this object, you can explicitly call the remove method to clear it.