In Java objects created in a heap memory, it is threads share a piece of space, we usually have a plenty of object held by the variable of the definition of a reference to the deposit, namely each thread to access the variable will be access to the same objects in the heap memory, so when using multithreading if need to manipulate the same resource, so need to think about thread safety problem.

// ThreadDemo.java

static int v = 0;

static void autoAdd(a){
    for (int i = 0; i < 10000; i++) { v++; }}public static void main(String[] args) throws Exception {
    new Thread(ThreadLocalDemo::autoAdd).start();
    autoAdd();
    Thread.sleep(1000);
    System.out.println(v);
}
Copy the code

The above code variable v may print less than 20000, and this problem will become more obvious as the number of v increments increases, such as the number of cycles increases to 100000.

Thread-safety issues can be addressed by synchronization or locking, but sometimes we want the variable to be unique to each thread, meaning that different threads should have separate copies of the value of the same variable.

Local variables should be familiar to Java developers. When different threads operate on a shared variable, we can create a new local variable that copies the value of the shared variable and then operate on it without contaminating the value of the shared variable. But for complex objects, copying can be cumbersome, and you can’t write the value back to a shared variable in this way.

There are ways to work around these issues, but it’s not necessary because Java provides ThreadLocal to help us create a thread-local variable.

The use of ThreadLocal

The use of ThreadLocal makes use of generics. We can put the desired value into a ThreadLocal, and specify the type of data to be stored in the ThreadLocal by specifying the generic type, just like a collection class.

ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
System.out.println(threadLocal.get());
Copy the code

The ThreadLocal we create with new is itself an object. If we want to get data, we call the get() method. The above code gets a NULL.

static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
    @Override
    protected Integer initialValue(a) {
        return 0; }};static void autoAdd(a) {
    for (int i = 0; i < 10000; i++) {
        int v = threadLocal.get();
        threadLocal.set(v + 1);
    }
    System.out.println(Thread.currentThread().getName() + "-- >" + threadLocal.get());
}

public static void main(String[] args) throws Exception {
    new Thread(ThreadLocalDemo::autoAdd).start();
    autoAdd();
    Thread.sleep(1000);
}
Copy the code

The result of the above code execution will look like this

Thread-0 --> 10000
main --> 10000
Copy the code

As you can see, the values obtained by the two threads are exclusive and do not interfere with each other because they break the element of the same resource, so there is no need to worry about thread safety.

How does ThreadLocal store data that is unique to each thread

We know that variables store references, so it is difficult to simply use variables to achieve thread-exclusivity. If we can understand what structure ThreadLocal uses to store these data, we can understand how it can achieve thread-exclusivity.

public T get(a) {
    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;
            returnresult; }}return setInitialValue();
}
Copy the code

We use ThreadLocal to get the exclusive value through the get() method, which fetches the Thread object from the current Thread and then fetches a ThreadLocalMap, a class that is similar in structure to a Java Map. But it does not implement the Map interface.

The following class diagram can be obtained by sorting out the related classes using IDEA

Each Thread in Java has a member variable, threadLocals, of type ThreadLocalMap, which defaults to NULL. When ThreadLocal calls getMap(Thread t), it only gets the corresponding Thread locals, and there is no complex operation.

// Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;

// ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
Copy the code

Similar to HashMap, ThreadLocalMap uses an array of entries as a hash table to store elements. Each Entry represents a key-value pair element. The value of the Entry member variable is the unique value of each thread, and the corresponding key, Is a ThreadLocal object that uses WeakReference for storage.

According to the annotation, the reason why WeakReference is used to reference keys is that it is convenient to deal with large Spaces that need to be used for a long time.

/** * To help deal with very large and long-lived usages, * the hash table entries use WeakReferences for keys. */
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;

        // ...
}
Copy the code

There is a member of ThreadLocalMap, threadLocals, which stores the corresponding value of each ThreadLocal. Since threadLocals vary from Thread to Thread, Therefore, the same ThreadLocal gets different values from different threads, which is the principle of value exclusivity in ThreadLocal.

Initialization of ThreadLocal

As mentioned earlier, using ThreadLocal requires overriding the initialValue() method because there is no way to directly assign an initialValue to a ThreadLocal variable when it is created.

When the threadLocals of the current thread is null when the get() method is called, or if the value of the current ThreadLocal is not available from threadLocals, The setInitialValue() method is called to set the initialValue to be stored in threadLocals, which is generated by the initialValue() method and will default to null if the method is not overridden.

private T setInitialValue(a) {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

// Initialize the value
protected T initialValue(a) {
    return null;
}
Copy the code

Since threadLocals defaults to null, createMap() will be called to create a ThreadLocalMap object if the resulting map is null. And supply the key and value of the first element to ThreadLocalMap.

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
Copy the code

ThreadLocal modifies data

You can modify values in a ThreadLocal using the set(T value) method as in the example above. When the set() method is called, createMap() is called to create and store the new value if threadLocals is not already created. Otherwise, call threadLocalmap. set(ThreadLocal
key, Object value).

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

Note that the set() method cannot be used instead of initialValue() to initialize the value of ThreadLocal. Because this requires a set call before each thread’s first use of ThreadLocal, this can be cumbersome.

In addition, even with ThreadLocal, the corresponding value is still an object, stored in heap memory. That is, any thread has access to that object. When we call set() on different threads to store the same object, ThreadLocal loses its thread-exclusive copy.

static class Demo {
    int v = 0;
}

static ThreadLocal<Demo> threadLocal = new ThreadLocal<Demo>() {
    @Override
    protected Demo initialValue(a) {
        return newDemo(); }};public static void main(String[] args) throws Exception {
    Demo demo = threadLocal.get();

    new Thread(() -> {
        threadLocal.set(demo);
        threadLocal.get().v = 100;
    }).start();

    Thread.sleep(100);
    System.out.println(threadLocal.get().v); / / 100
}
Copy the code

The output from executing the code above will be 100. This makes sense because the value of a ThreadLocal is the same object in both threads, so changes made by one thread will affect the other threads.

Some details of ThreadLocalMap

Unlike a HashMap, ThreadLocalMap uses threadLocalHashCode instead of the hashCode of a ThreadLocal object to get an Entry subscript.

// Get the corresponding Entry subscript from ThreadLocalMap
// Key is a ThreadLocal object
int i = key.threadLocalHashCode & (table.length - 1);
Copy the code

This value is generated by the getAndAdd() method of ThreadLocal, a static member of AtomicInteger type nextHashCode.

// ThreadLocal
private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode(a) {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code

In addition, hash conflicts may occur in the hash table. ThreadLocalMap uses closed hashes to handle hash conflicts.

private void set(ThreadLocal
        key, Object value) {

    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

Memory overflow problem

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

Memory leaks are a concern when using ThreadLocal. However, with ThreadLocal, memory leaks occur not in ThreadLocal objects but in the corresponding values stored in the Entry.

ThreadLocal objects do not leak memory. When we set the reference to ThreadLocal to null, it is a weak reference to ThreadLocal due to the Entry in the ThreadLocalMap of the Thread. Therefore, it is collected when GC occurs.

So there may be an Entry with a null key in a ThreadLocalMap, but since the value of this Entry is a strong reference to the referenced object, the referenced object will not be reclaimed, which is why a memory leak occurs.

ThreadLocalMap has the same life cycle as a thread. When the thread finishes running, the thread is destroyed and the ThreadLocalMap object, stored Entry objects, and objects referenced by value are reclaimed.

However, if you use a thread pool, the thread does not destroy the task, it is put back into the pool, and the corresponding value is always there, which is why a memory leak occurs.

ThreadLocal provides the remove() method, which can set the reference of WeakReference to ThreadLocal and the reference of value to Object in the Entry of ThreadLocal corresponding to the current thread to null. At the same time, the Entry object is removed.

When ThreadLocal is used up, remove() should be called to prevent memory leaks before the reference is set to NULL.

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

// ThreadLocalMap
private void remove(ThreadLocal
        key) {
    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)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return; }}}private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // ...
}

// WeakReference --> Reference
public void clear(a) {
    this.referent = null;
}
Copy the code

ThreadLocal uses a little trickery

ThreadLocal is declared as static as possible

A ThreadLocal variable should be declared static, because it should be accessed by the same ThreadLocal object for each thread. If it is not declared static, you need to create an object corresponding to the class of the ThreadLocal to use it. If we create multiple objects, we will have multiple ThreadLocal’s with the same function, which is just increasing memory consumption.

WithInitial () matches a Lambda expression

Defining ThreadLocal initialization can also be done by calling withInitial() and passing in the Supplier object, which is an interface and the only method to override is get(), So we can use it with Lambda expressions or Method references.

static ThreadLocal<Integer> threadLocal1 = ThreadLocal.withInitial(() -> 0);
static ThreadLocal<Demo> threadLocal2 = ThreadLocal.withInitial(Demo::new);
Copy the code

The withInitial() method returns a SuppliedThreadLocal object. The SuppliedThreadLocal inherits most of its functionality from ThreadLocal, but it helps us override the initialValue() method, The initialization value is obtained by calling the get() method of the passed Supplier object.

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

    private final Supplier<? extends T> supplier;

    SuppliedThreadLocal(Supplier<? extends T> supplier) {
        this.supplier = Objects.requireNonNull(supplier);
    }

    @Override
    protected T initialValue(a) {
        returnsupplier.get(); }}Copy the code

reference

  • Understand ThreadLocal in Java
  • An in-depth analysis of Red Rose-threadLocal memory leaks