A scenario using Threadlocal to solve concurrency and efficiency problems

ThreadLocal is a tool provided by JDK 1.2, a tool mainly to solve the problem of sharing resources under multi-threaded, In the next section, we will analyze how ThreadLocal can be used to solve concurrency problems and improve code efficiency in development, starting from the definition of ThreadLocal and its application scenarios.

Scenario 1, ThreadLocal is used to save objects that are unique to each thread, creating a copy for each thread so that each thread can modify the copy it owns without affecting the other threads’ copies, ensuring thread safety.

Scenario 2, ThreadLocal, is used in scenarios where information needs to be stored independently within each thread so that other methods can more easily access that information. The information obtained by each thread may be different. After the information is saved by the method executed earlier, subsequent methods can obtain it directly through ThreadLocal, avoiding passing references, similar to the concept of global variables.

Multi-threaded threads are not safe if they are not concurrently handled, next we use sample code to demonstrate (assuming 100 threads need to use SimpleDateFormat output time).

public class ThreadLocalTest {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);
    public static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            threadPool.submit(() -> {
                String date = ThreadLocalTest.date(finalI);
                System.out.println(date);
            });
        }
        threadPool.shutdown();
    }

    public static String date(int seconds) {
        Date date = new Date(1000 * seconds);
        return dateFormat.format(date);
    }
}
Output:
00:05
00:02
00:05
00:01
00:04
00:16
00:17
00:19

If you execute the above code, you will see that the console is not printing what we expect. What we expect is that the time printed is not repeated, but we can see that there is a repeat here, for example, the first line and the third line are both 05 seconds, which means that it has a concurrency problem. You might think of using locks to solve the concurrency problem.

 public synchronized static String date(int seconds) {
        Date date = new Date(1000 * seconds);
        return dateFormat.format(date);
    }

which is a solution, but the code execution will be much less efficient.

Analyzing this, if we could make each thread have its own SimpleDateFormat object, the concurrency and efficiency problems would be solved.

public class ThreadLocalTest {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(16);

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            threadPool.submit(() -> {
                String date = date(finalI);
                System.out.println(date);
            });
        }
        threadPool.shutdown();
    }

    public static String date(int seconds) {
        Date date = new Date(1000 * seconds);
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));
}

How to Store in Thread

There is only one ThreadLocalMap inside a Thread, but there can be many ThreadLocals inside a ThreadLocalMap, and each ThreadLocal corresponds to a value.

Since a Thread can call more than one ThreadLocal, Thread uses a Map data structure like ThreadLocalMap to store ThreadLocal and value.

Let’s take a look at the ThreadLocalMap internal class

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;
        }
    }
   private Entry[] table;
   //...
}

The ThreadLocalMap class is a member variable inside the Thread class of each thread, the most important of which is the Entry internal class in the code truncated here. In ThreadLocalMap, there is an array of Entry type named table, and we can think of Entry as a map with the following key-value pairs.

  • the key, the current ThreadLocal.
  • value, the actual variable to be stored, such as user user object or simpleDateFormat object, etc.

Since ThreadLocalMap is similar to Map, it has a series of standard operations including set, get, rehash, resize, etc., just like HashMap. However, although the idea is similar to HashMap, there are some differences in the implementation.

For example, one of the differences is that we know that HashMap uses the zipper method when facing hash conflicts.

But ThreadLocalMap resolves hash conflicts in a different way, it uses the linear detection method. If a conflict occurs, it will not be chained down, but will continue to look for the next empty grid. This is the point where ThreadLocalMap and HashMap are different in handling conflicts

Note on usage

Key leakage

We just introduced ThreadLocalMap, and for every ThreadLocal there is a ThreadLocalMap

Although we might set the ThreadLocal instance to null by doing something like this, thinking that we can rest easy

However, after rigorous GC reachability analysis, even though we set the ThreadLocal instance to null in the business code, the reference chain still exists in the Thread class.

GC will do reachability analysis during garbage collection, and it will find that this ThreadLocal object is still reachable, so it will not garbage collect this ThreadLocal object, which will cause memory leak. This results in a memory leak, which leads to OOM.

Considering this danger, the Entry in ThreadLocalMap inherits the WeakReference weak reference.

static class Entry extends WeakReference<ThreadLocal<? > > {

    /* The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<? > k, Object v) {

        super(k);
        value = v;
    }
}

As you can see, this Entry is extends WeakReference. the feature of weak reference is that if this object is only associated by weak reference and not by any strong reference, then this object can be reclaimed, so weak reference will not prevent GC. therefore, this weak reference mechanism avoids the memory leak problem of ThreadLocal.

Value Leakage

Let’s think carefully, each Entry of ThreadLocalMap is a weak reference to key, but this Entry contains a strong reference to value

A strong reference means that our variable will always be in our memory when the thread is not finished

Author is a warm-hearted guy and takes this into account for us. When executing the set, remove, rehash, etc. methods of ThreadLocal, it scans for Entries with a null key, and if it finds an Entry with a null key, it means that its corresponding If the key of an Entry is found to be null, it means that the value corresponding to it is also useless, so it will set the corresponding value to null, so that the value object can be recycled normally.

However, if ThreadLocal is no longer used, then actually set, remove, and rehash methods will not be called, and at the same time, if the thread stays alive and does not terminate, then the memory will never be GC, which leads to a memory leak of value, which leads to OOM.

To avoid the above situation, we should manually call the remove method of ThreadLocal after using it, in order to prevent memory leaks from happening.

The purpose is to prevent memory leaks.

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m ! = null)
        m.remove(this);
}

In the remove method, you can see that it gets the reference to ThreadLocalMap first and calls its remove method. The remove method here can clean up the value corresponding to the key, so that the value can be reclaimed by GC.

Summary

In this article, we introduced the ThreadLocal application scenario and demonstrated the code for the application scenario; we learned how ThreadLocal is stored in threads; and we learned the proper way to use ThreadLocal.