ThreadLocal
1. Function of ThreadLocal
Usually, the variables we create can be accessed and modified by any thread, but in the environment of multithreading, we want each thread to have its own local exclusive variables. How to implement it? (for example, each thread saves its unique UserId and TranactionId)
Similar to allocating memory in the JVM, the Java heap shared by all threads can be divided into multiple thread private allocation buffers TLAB(Thread Local Allocation Buffer) to improve the efficiency of object allocation
Java's JDK provides a solution: ThreadLocal
2. Use of ThreadLocal
Web application is a typical multitask reference. Java provides thread pool, which can easily execute these request tasks and reuse threads;
Each user's request will create a task Runnable
Runnable task = ()->{ public void UserPorcess(User user){ checkPermisson(); loadResouce(); doWork(); saveStatus(); sendResponse(); } } // Run in thread pool executor.execute(task);
The user parameter user is passed in. How to transfer the status in a thread?
- 1. Pass the user parameter into all methods
- 2. To save the user information to the local thread, you need to execute the get() method to get the user information
Context:
In a thread, the object that needs to be passed across several method calls is usually called context, which is a state.
For the first method, the user parameter is passed into all methods, and no matter how complex each method adds a context parameter, once the third-party library is called, the code cannot be modified, and the user parameter cannot be passed in.
TheadLocal is to save the information package in the local thread and pass the same object in one thread
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method has its own, independently initialized copy of the variable. 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).
Applicable scenarios:
- Each thread needs to have its own instance
- Instances need to be shared in multiple methods, but they do not want to be shared by multiple threads
Examples of storing user session s
// Private static private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws Exception { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (Exception ex) { throw new Exception(ex); } return s; }
3. Implementation of ThreadLocal
3.1 ThreadLocal overview
There can be multiple ThreadLocal objects in a thread, so ThreadLocal actually maintains a Map - ThreadLocalMap
/* * <p>Each thread holds an implicit reference to its copy of a thread-local * variable as long as the thread is alive and the {@code ThreadLocal} * instance is accessible; after a thread goes away, all of its copies of * thread-local instances are subject to garbage collection (unless other * references to these copies exist). */ public class ThreadLocal<T> { private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } protected T initialValue() { return null; } public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } public ThreadLocal() { } /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { 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(); } /** * Returns {@code true} if there is a value in the current thread's copy of * this thread-local variable, even if that values is {@code null}. * * @return {@code true} if current thread has associated value in this * thread-local variable; {@code false} if not */ boolean isPresent() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); return map != null && map.getEntry(this) != null; } /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this); } return value; } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } } /** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * {@code initialValue} method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } }
set and get method flow
- set
- get
Each Thread maintains its own threadLocals variable, so when each Thread creates ThreadLocal, in fact, the data is stored in the threadLocals variable of its own Thread, which can not be obtained by other threads, so isolation is realized.
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; }
3.2ThreadLocalMap
Static inner class of ThreadLocal when ThreadLocalMap
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
3.3 problems caused by ThreadLocal - memory leakage
WeakReference is a weak reference. The weakly referenced object will be recycled in the next GC (no matter whether the memory is enough or not)
static class Entry extends WeakReference<ThreadLocal<?>>
But value is a strong reference
Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }
The value in the Entry object may not be recycled all the time, resulting in memory leakage
Solution:
The remove() method is called in the finally statement of the current thread.
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } }
4. Summary
4.1 application scenario
- In many scenarios of Web applications, cookie, session and other data isolation are implemented through ThreadLocal
- Transaction synchronization manager of Spring framework
- SimpleDataFormat multithreading security problem solving
4.2 usage
- To prevent memory leakage, you need to call remove() in the last finally statement.