As for ThreadLocal, we often use it to solve multi-threaded concurrency problems. How does it work? Let’s take a look today.

Start with the source code

First, let’s look at the introduction of the ThreadLocal class:

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).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the 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).

As described in this article, ThreadLocal provides thread-local variables, with each thread having a separate copy, often in the form of private static variables. The key is the next section, the thread is alive, the ThreadLocal instance can be accessed, the thread is gone, and the garbage is collected.

The get () method

Do you remember the type of reference mentioned in the last article? It could be soft or weak. What is it? Let’s look at the code:

Public T get() {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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }Copy the code

The key map is the threadLocals variable in the Thread class. Let’s look at the source code for ThreadLocalMap:

ThreadLocal.ThreadLocalMap threadLocals = null; static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) {// use ThreadLocal as key and weak reference super(k); value = v; }} // omit code}Copy the code

As described in the previous article, if an object has only weak references, the object will be reclaimed when the next GC occurs. So let’s sort this out:

  1. The Entry of a ThreadLocalMap refers to a ThreadLocalA weak reference.
  2. ThreadLocal itself does not store values; specific values remain in individual threads. So you can think of ThreadLocal as a utility class.

Note that in Entry, only key is a weak reference, but value is still a strong reference. Will there be a case where the map’s key is null but the value still exists after the key is garbage collected?

The set () method

It is possible, but the JDK itself has been optimized to do so. Take a look at the set() method of ThreadLocalMap:

private void set(ThreadLocal<? > key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new  entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

When ThreadLocalMap calls set(), when it detects an entry with a null key, it sets value to null, so that the instance before value can be reclaimed.

Usage scenarios

Simple to use

Let’s start with a simple example:

public class ThreadLocalSimpleDemo { public static void main(String[] args) { int threads = 3; InnerClass innerClass = new InnerClass(); for (int i = 1; i <= threads; i++) { new Thread(() -> { for (int j = 0; j < 4; j++) { innerClass.add(String.valueOf(j)); innerClass.print(); } innerClass.set("hello world"); }, "thread - " + i).start(); }} private static class InnerClass {/** * add */ public void add(String newStr) {StringBuilder STR = Counter.counter.get(); Counter.counter.set(str.append(newStr)); Public void print() {system.out.printf (" threadName :%s, ThreadLocal hashCode :%s, Instance hashcode:%s, Value:%s\n", Thread.currentThread().getName(), Counter.counter.hashCode(), Counter.counter.get().hashCode(), Counter.counter.get().toString() ); } public void set(String words) {counter.counter. set(new StringBuilder(words)); System.out.printf( "Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n", Thread.currentThread().getName(), Counter.counter.hashCode(), Counter.counter.get().hashCode(), Counter.counter.get().toString() ); Private static ThreadLocal<StringBuilder> Counter = private static ThreadLocal<StringBuilder> Counter =  ThreadLocal.withInitial(StringBuilder::new); }}Copy the code

The printed result is:

Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:01
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0123
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:012
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0123
Set, Thread name:thread - 1 , ThreadLocal hashcode:310471657,  Instance hashcode:820066274, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:012
Set, Thread name:thread - 2 , ThreadLocal hashcode:310471657,  Instance hashcode:155293473, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0123
Set, Thread name:thread - 3 , ThreadLocal hashcode:310471657,  Instance hashcode:1804272849, Value:hello worldCopy the code

As you can see, when we use ThreadLocal, we are using the same object, but the corresponding instance of each thread is different. After the set() method is called, the corresponding instance is replaced.

Session

For Java Web applications, sessions hold a lot of information. Most of the time, you need to obtain information from the Session, and sometimes you need to modify the Session information. On the one hand, you need to ensure that each thread has its own separate Session instance. On the other hand, since sessions are required in many places, there is a need for multiple ways to share sessions. Implementation using ThreadLocal:

public class SessionHandler { public static ThreadLocal<Session> session = ThreadLocal.<Session>withInitial(() -> new Session()); @Data public static class Session { private String id; private String user; private String status; } public String getUser() { return session.get().getUser(); } public String getStatus() { return session.get().getStatus(); } public void setStatus(String status) { session.get().setStatus(status); } public static void main(String[] args) { new Thread(() -> { SessionHandler handler = new SessionHandler(); handler.getStatus(); handler.getUser(); handler.setStatus("close"); handler.getStatus(); }).start(); }}Copy the code

conclusion

ThreadLocal is simple to use, but it’s worth getting to know, given the sophistication of its design.

If you are interested, you can visit my blog or pay attention to my public number and headline number. Maybe there will be unexpected surprises.

death00.github.io/