What is a ThreadLocal

The ThreadLocal class can be understood as a thread-local variable, as the name implies. That is, if you define a ThreadLocal, each thread that reads or writes to that ThreadLocal is thread isolated and does not affect each other. It provides a mechanism for thread closure by having a separate copy of mutable data for each thread.

The practical application

The actual use of ThreadLocal in development is rare, mostly within the framework. The most common use scenario is to use it for database connections, Session management, and so on to ensure that each thread is using the same database connection. There is another scenario that has been used a lot to address the issue of SimpleDateFormat being thread-safe, but java8 now provides DateTimeFormatter which is thread-safe for those interested. It can also be used to gracefully pass parameters that are lost if a parent thread passes a variable or parameter directly to a child thread through a ThreadLocal. We’ll introduce another ThreadLocal for this later.

ThreadLocal API is introduced

ThreadLocal still has a few apisLet’s take a look at these guysapiAnd it’s super easy to use

    private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(()->"Java financial");
    public static void main(String[] args) {
        System.out.println("Get initial value:"+threadLocal.get());
        threadLocal.set("Focus: [Java Finance]");
        System.out.println("Get modified value:"+threadLocal.get());
        threadLocal.remove();
    }
Copy the code

Output result:

Get initial value: Java Finance Get modified value: concern:Copy the code

Isn’t it easy to cook a chicken? Just a few lines of code cover all the apis. Let’s take a brief look at the source code of these apis.

Member variables

        /* The initial capacity MUST be a power of two. */
        private static final int INITIAL_CAPACITY = 16;

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

        /** * The number of entries in the table. */
        private int size = 0;

        /** * The next size value at which to resize. */
        private int threshold; // Default to 0
Copy the code

Here’s a common interview question: Why must the size of an entry array, and its initial capacity, be a power of two? For firstKey. ThreadLocalHashCode & (INITIAL_CAPACITY – 1); And many sources use hashCode & (2n2^n2n-1) instead of hashCode% 2n2^n2n. The advantages of this method are as follows:

  • Bit operation is used instead of modulus taking to improve computational efficiency.
  • To make a differencehashValues are less likely to collide, as much as possible to hash elements evenly in the hash table.

Set method

   public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            createMap(t, value);
    }
Copy the code

ThreadLocalMap (java.util. Map); java.util. Map (java.util. The key is a ThreadLocal, but in fact the ThreadLocal contains a weak reference to the ThreadLocal, and its value is the value of the set

   static class Entry extends WeakReference<ThreadLocal<? >>{
            /** The value associated with this ThreadLocal. */
            Object value; // The actual stored valueEntry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code

EntryIs isThreadLocalMapThe node defined in. It inheritsWeakReferenceClass that defines a class of typeObjectthevalueUsed to store the plugThreadLocalIn value. Let’s look at this one againThreadLocalMapWhere is it located? We seeThreadLocalMapIs located in theThreadA variable in there, and our value is in thereThreadLocalMapIn this way, we achieve isolation between each thread. The following two graphs are basically the sameThreadLocalThe structure of the. Now let’s seeThreadLocalMapThe data structure, we knowHaseMapTo solvehashConflicts are caused by linked lists and red-black trees (jdk1.8), but we’ll seeThreadLocalMapThere’s only one array. How does that workhashConflict?ThreadLocalMapusingLinear detectionWhat is linear detection? Is the rootAccording to the initialkeyThe hashcode value of thetablePosition in an array, if you find that there are already other positions in that positionkeyIf the element of value is occupied, the fixed algorithm is used to find the next position of a certain step length, and judge successively until the position that can be stored is found.ThreadLocalMapTo solveHashThe way to conflict is simply to add step size1Or minus1Find the next adjacent location.


        /** * Increment i modulo len. */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /** * Decrement i modulo len. */
        private static int prevIndex(int i, int len) {
            return ((i - 1> =0)? i -1 : len - 1);
        }
Copy the code

This can cause performance problems if a thread has a large number of ThreadLocal’s, because the table needs to be traversed every time, emptying invalid values. Therefore, we should use as few ThreadLocal as possible. We should not create a large number of ThreadLocal in the thread. If we need to set different parameter types, we can use ThreadLocal to store an Object Map. You can greatly reduce the number of ThreadLocal’s created. The pseudocode is as follows:

public final class HttpContext {
    private HttpContext(a) {}private static final ThreadLocal<Map<String, Object>> CONTEXT = ThreadLocal.withInitial(() -> new ConcurrentHashMap(64));
    public static <T> void add(String key, T value) {
        if(StringUtils.isEmpty(key) || Objects.isNull(value)) {
            throw new IllegalArgumentException("key or value is null");
        }
        CONTEXT.get().put(key, value);
    }
    public static <T> T get(String key) {
        return (T) get().get(key);
    }
    public static Map<String, Object> get(a) {
        return CONTEXT.get();
    }
    public static void remove(a) { CONTEXT.remove(); }}Copy the code

In this case, if we need to pass different parameters, we can simply use one ThreadLocal instead of multiple ThreadLocal. If I don’t want to do that, I’m going to create multiple ThreadLocal’s. That’s what I want, and I want performance to be good. You can use Netty’s FastThreadLocal to solve this problem, but with fastThreadLocalThreads or a subclass of fastThreadLocalThreads, threads are more efficient. Check out more about using fastThreadLocalThreads.

So let’s first look at this hash function

    // Generate hash code gap for this magic number, so that the generated values or ThreadLocal ids are evenly distributed in an array of powers of 2.
    private static final int HASH_INCREMENT = 0x61c88647;

    /** * Returns the next hash code. */
    private static int nextHashCode(a) {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
Copy the code

As you can see, it adds a magic number 0x61C88647 to the ID/threadLocalHashCode of the last constructed ThreadLocal. The magic number is chosen in relation to the Fibonacci hash, and 0x61C88647 corresponds to decimal 1640531527. When we add 0x61C88647 to each ThreadLocal to assign its own ID, threadLocalHashCode modulo with a power of 2 (the length of the array), the results are evenly distributed. And we can do that by using this magic number

public class MagicHashCode {
    private static final int HASH_INCREMENT = 0x61c88647;

    public static void main(String[] args) {
        hashCode(16); // Initialize 16
        hashCode(32); // Double the capacity
        hashCode(64);
    }

    private static void hashCode(Integer length) {
        int hashCode = 0;
        for (int i = 0; i < length; i++) {
            hashCode = i * HASH_INCREMENT + HASH_INCREMENT;// Increment HASH_INCREMENT each time
            System.out.print(hashCode & (length - 1));
            System.out.print(""); } System.out.println(); }}Copy the code

Running results:

7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0 
7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 38 45 52 59 2 9 16 23 30 37 44 51 58 1 8 15 22 29 36 43 50 57 0 

Copy the code

I have to give this author credit, by using Fibonacci hashing, to keep the hash table discrete, to make it uniform. So if you want to write good code, you still need math. Other source code is not analyzed, we are interested in you can go to view.

Memory leak in ThreadLocal

Whether ThreadLocal causes memory leaks is also a more debatable issue. First we need to know what is a memory leak?

In Java, a memory leak is the existence of some allocated objects. These objects have the following two characteristics. First, these objects are reachable, that is, in the directed graph, there are channels to connect to them. Second, these objects are useless, that is, the program will never use them again. If an object meets these two conditions, it can be considered a memory leak in Java; it will not be collected by GC, but it will consume memory.

ThreadLocal memory leak:

  • Threads have a long life cycle whenThreadLocalWhen it is not strongly referenced externallyGCRecycling (toThreadLocalEmpty) :ThreadLocalMapThere will be akeyfornulltheEntry, but theEntrythevalueIt will never be accessibleThe set and getAnd so on. If this thread never ends, then thiskeyfornulltheEntryBecause strong references also exist (Entry.value), andEntryBy the current threadThreadLocalMapStrong reference (Entry[] table), leading to thisEntry.valueNever to beGC, causing a memory leak.

Let’s demonstrate this scenario

 public static void main(String[] args) throws InterruptedException {
        ThreadLocal<Long []> threadLocal = new ThreadLocal<>();
        for (int i = 0; i < 50; i++) {
            run(threadLocal);
        }
        Thread.sleep(50000);
        // Remove strong references
        threadLocal = null;
        System.gc();
        System.runFinalization();
        System.gc();
    }

    private static void run(ThreadLocal<Long []> threadLocal) {
        new Thread(() -> {
            threadLocal.set(new Long[1024 * 1024 *10]);
            try {
                Thread.sleep(1000000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
Copy the code

throughjdkBuilt-in toolsjconsole.exeYou will find that even if it is executedgcMemory is not reduced because the key is still being strongly referenced by the thread. The renderings are as follows:

  • In this case

ThreadLocalMap is designed to allow this to happen. Whenever you call the set(), get(), and remove() methods, the cleanSomeSlots() and expungeStaleEntry() methods will be called to remove values whose key is null. This is a passive cleanup, but if ThreadLocal’s set(), get(), and remove() methods are not called, it will result in a memory leak for value. Its documentation recommends that we use static-modified ThreadLocal, resulting in the lifetime of a ThreadLocal being as long as the class that holds it. Since ThreadLocal has strong references, this means that ThreadLocal will not be GC. In this case, if we do not manually delete the Entry key, the Entry key is never null, and the weak reference is meaningless. So we try to form a good habit when using, after using manually call remove method. In fact, in the actual production environment, most cases of manual remove are not to avoid the situation where the key is null, but to ensure the correctness of services and programs. For example, after placing an order, we construct the context request information of the order through ThreadLocal, and then asynchronously update the user score through the thread pool. At this time, if the update is completed without removing operation, even if the new order will cover the original value, it may cause business problems. If you do not want to manually clean, is there another way to solve the following? Take a look at FastThreadLocal, which provides automatic collection.

  • In the thread pool scenario, the program does not stop, and the thread will not be destroyed if it is constantly reused, which is essentially the same as the example above. If the thread is not reused, it is destroyed and there is no leakage. Because at the end of the threadjvmTake the initiative to callexitMethod cleaning.
      /** * This method is called by the system to give a Thread * a chance to clean up before it actually exits. */
        private void exit(a) {
            if(group ! =null) {
                group.threadTerminated(this);
                group = null;
            }
            /* Aggressively null out all reference fields: see bug 4006245 */
            target = null;
            /* Speed the release of some of these resources */
            threadLocals = null;
            inheritableThreadLocals = null;
            inheritedAccessControlContext = null;
            blocker = null;
            uncaughtExceptionHandler = null;
        }
Copy the code

InheritableThreadLocal

I mentioned at the beginning of this article that variable passes are lost between parent and child threads. But InheritableThreadLocal provides a mechanism to share data between parent and child threads. Can solve this problem.

 static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set(ThreadLocal Main thread value);
        Thread.sleep(100);
        new Thread(() -> System.out.println(The child thread gets the main thread value of threadLocal: + threadLocal.get())).start();
        Thread.sleep(100);
        inheritableThreadLocal.set("InheritableThreadLocal main thread value");
        new Thread(() -> System.out.println([inheritableThreadLocal] [inheritableThreadLocal] + inheritableThreadLocal.get())).start();

    }
Copy the code

The output

The thread gets the main thread value of threadLocal:nullThe child thread retrieves the primary value of the inheritableThreadLocal threadCopy the code

But there are problems with using InheritableThreadLocal and thread pools, The child thread copies data from the parent thread’s inheritableThreadLocals to its own inheritableThreadLocals only when the thread object is created. This enables context passing between parent and child threads. But with thread pools, threads are reusable, so there’s a problem. What can be done to solve this problem? Think about it, or leave a comment below. If you don’t want to have any thoughts if there is any issues with Alibaba, please refer to the website if there is any issues with Alibaba-thread-local.

conclusion

  • Basically introducedThreadLocalCommon usage, as well as general implementation principles, and aboutThreadLocalMemory leaks, what to be careful about using it, and how to resolve passes between parent and child threads. This paper introduces theThere is ThreadLocal, InheritableThreadLocal, FastThreadLocal, and transmittable-thread-localVarious usage scenarios and precautions. This paper focuses on the introductionThreadLocalIf you make this clear, the other kinds of ThreadLocal are a little more understandable.

The shoulders of giants pick apples: zhuanlan.zhihu.com/p/40515974 www.cnblogs.com/aspirant/p/… www.cnblogs.com/jiangxinlin… Blog.csdn.net/hewenbo111/…