ThreadLocal source code analysis and possible memory leak problems

What is a ThreadLocal

ThreadLocal is a solution to multi-threaded concurrent access.

/** * This class provides thread-local variables. These variables differ from * their normal counterparts in that each thread that accesses one (via its * {@code get} or {@code set} method) has its own, independently initialized * copy of the variable. {@code ThreadLocal} instances are typically private * static fields in  classes that wish to associate state with a thread (e.g., * a user ID or Transaction ID). #### */Copy the code

ThreadLocal can store variables in a thread, except that each thread reads variables independently of each other. Compared with Synchonized, which uses a locking mechanism so that variables or code blocks can only be accessed by one thread at a time, ThreadLocal provides each thread with its own copy of variables. In this way, each thread does not access the same object at the same time, so as to realize the problem of data sharing by multiple threads.

The use of ThreadLocal

  • void set(Object value)

Sets the value of a thread-local variable for the current thread.

  • public Object get()

This method returns the thread-local variable corresponding to the current thread.

  • public void remove()

Delete the value of the current thread-local variable in order to reduce memory usage. This method is new in JDK 5.0. It is important to note that local variables to the thread are automatically garbage collected when the thread terminates, so it is not necessary to explicitly call this method to clean up local variables of the thread, but it can speed up memory collection.

  • protected Object initialValue()

Returns the initial value of the thread-local variable, which is a protected method, obviously designed to be overridden by subclasses. This method is a deferred call that is executed only once, the first time the thread calls GET () or set(Object). The default implementation in ThreadLocal simply returns null

public final static ThreadLocal RESOURCE = new ThreadLocal(); RESOURCE represents a ThreadLocal object that can hold strings. No matter what thread can concurrently access the variable, write or read the variable, it is thread safe.

Source code analysis

When you call ThreadLocal’s get method, you get the ThreadLocalMap of the current thread, i.e., one ThreadLocalMap for each thread

Thread t = Thread.currentThread(); 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; } } return setInitialValue(); }Copy the code

An Entr is a ThreadLocal object that stores values as values. Arrays are used to handle multiple variables

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

So getEntry gets the value of a ThreadLocal, and it’s worth noting that the key is a weak reference

Memory leak analysis

Through source code analysis, each Thread maintains a ThreadLocalMap. The Key of this map is ThreadLocal, the Value is the Value to be stored, and the Key is used as a weak reference. Weak reference objects are collected during GC. ThreadLocal objects are used as keys, and ThreadLocalMap and Thread have the same life cycle. When ThreadLocal objects are reclaimed, if the current Thread is not terminated, the key will be null and the value cannot be accessed, resulting in memory leakage. When ThreadLocal’s get or set methods are called at some point, the expungeStaleEntry method is called to remove the null Key Value from the Entry. This is not timely and may not be performed every time. So you need to call the remove() method after use to show that the expungeStaleEntry method is called for recycling. The essential problem is that ThreadLocalMap has a lifetime as long as Thread, and memory leaks can result if the Thread is not removed in time. The use of weak references increases the chances of recycling, somewhat avoiding leakage. When using thread pools and ThreadLocal, be aware that threads are constantly repeating, and not manually deleting them will result in the accumulation of values.

Android ThreadLocal application

We know that Handler has a separate Looper object in the Handler thread, which uses ThreadLocal.

// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; /** * Initialize the current thread as a looper, marking it as an * application's main looper. The main looper for your application * is created by the Android environment, so you should never need * to call this function yourself. See also: {@link #prepare()} */ public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper ! = null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static @Nullable Looper myLooper() { return sThreadLocal.get(); } /** Initialize the current thread as a looper. * ...... */ public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } /** * Returns the application's main looper, which lives in the main thread of the application. */ public static Looper getMainLooper() { synchronized (Looper.class)  { return sMainLooper; }}Copy the code

PrepareMainLooper will start a Looper in the entry method of the App when prepareMainLooper is started. The child thread will also display the Looper object that calls Looper.prepare() to generate the current thread. Looper is a ThreadLocal mechanism that ensures that each thread has its own Looper and that threads do not interfere with each other.