Java Concurrency: A brief introduction to ThreadLocal

Java Concurrency: A brief introduction to ThreadLocal

Author: glutinous rice balls

Personal blog: javalover.cc

Preface

In the previous section on thread security, global variables (member variables) and local variables (variables in methods or code blocks) were introduced. The former is unsafe in multithreading and requires mechanisms such as locking to ensure safety, while the latter is Thread-safe, but cannot be shared between multiple methods

Today s protagonist ThreadLocal fills the gap between global variables and local variables

Introduction

ThreadLocal has two main functions:

  1. Data isolation between threads: a copy is created for each thread, and threads cannot access each other

  2. Simplification of passing parameters: The copy created for each thread is globally visible in a single thread, and does not need to be passed back and forth between multiple methods

In fact, the above two effects are the credit of the copy in the final analysis, that is, each thread creates a copy separately, which produces the above effect

ThreadLocal literally translates to thread local variables, cleverly combining the advantages of both global variables and local variables

Below we give two examples to illustrate its role

table of Contents

  1. Example-data isolation
  2. Example-Passing parameter optimization
  3. Internal principle

text

When we come into contact with a new thing, we should first use it first, and then explore the internal principles

The use of Thread Local is relatively simple, similar to Map, various put/get

Its core methods are as follows:

  • public void set(T value)
    : Save the current copy to ThreadLocal, each thread is stored separately
  • public T get()
    : Take out the copy just saved, each thread will only take out its own copy
  • protected T initialValue()
    : Initialize the copy, the effect is the same as set, but initialValue will be executed automatically, if get() is empty
  • public void remove()
    : Delete the copy just saved

1. Example-data isolation

Here we use SimpleDateFormat as an example, because this class is thread-unsafe (there will be a separate chapter later), if there is no isolation, there will be various concurrency problems

Let's first look at an example of thread insecurity , the code is as follows:

public class ThreadLocalDemo { //Thread is not safe: when executed in multiple threads, there may be parsing errors private SimpleDateFormat simpleDateFormat = new SimpleDateFormat( "yyyy-MM-dd" ); public void parse (String dateString) { try { System.out.println(simpleDateFormat.parse(dateString)); } catch (ParseException e) { e.printStackTrace(); } } public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool( 10 ); ThreadLocalDemo demo = new ThreadLocalDemo(); for ( int i = 0 ; i < 30 ; i++) { service.execute(()->{ demo.parse( "2020-01-01" ); }); } } } Copy code

Run multiple times, the following error may appear:

In the Thread Exception "the Thread-the pool-1-4" java.lang.NumberFormatException: empty String copy the code

Regarding the insecurity of SimpleDateFormat, it is mentioned in the source code comments, as follows:

Formats are not DATE the synchronized . It IS Recommended to the Create separate format instances for the each the Thread. The If Access Multiple Threads A format concurrently, the MUST IT BE the synchronized externally. Copy the code

It means that when it is recommended to use multiple threads, either each thread is created separately or locked

Below we use locking and separate creation to solve

Example of thread safety : locking

public class ThreadLocalDemo { //Thread safety 1: add built-in lock private SimpleDateFormat simpleDateFormatSync = new SimpleDateFormat( "yyyy-MM-dd" ); public void parse1 (String dateString) { try { synchronized (simpleDateFormatSync){ System.out.println(simpleDateFormatSync.parse(dateString)); } } catch (ParseException e) { e.printStackTrace(); } } public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool( 10 ); ThreadLocalDemo demo = new ThreadLocalDemo(); for ( int i = 0 ; i < 30 ; i++) { service.execute(()->{ demo.parse1( "2020-01-01" ); }); } } } Copy code

Thread-safe example: Create a copy for each thread through ThreadLocal

public class ThreadLocalDemo { // 2 ThreadLocal // withInitialValue private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){ // 10 SimpleDateFormat 10 @Override protected SimpleDateFormat initialValue() { // 10 id System.out.println(Thread.currentThread().getId()); return new SimpleDateFormat("yyyy-MM-dd"); } }; public void parse2(String dateString){ try { System.out.println(threadLocal.get().parse(dateString)); } catch (ParseException e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); ThreadLocalDemo demo = new ThreadLocalDemo(); for (int i = 0; i < 30; i++) { service.execute(()->{ demo.parse2("2020-01-01"); }); } } }

SimpleDateFormat

ThreadLocal

2. -

ThreadLocal

public class ThreadLocalDemo2 { // public void fun1(int age){ System.out.println(age); fun2(age); } private void fun2(int age){ System.out.println(age); fun3(age); } private void fun3(int age){ System.out.println(age); } public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); ThreadLocalDemo2 demo = new ThreadLocalDemo2(); for (int i = 0; i < 30; i++) { final int j = i; service.execute(()->{ demo.fun1(j); }); } } }

ThreadLocal

public class ThreadLocalDemo2 { // ThreadLocal private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); public void fun11(){ System.out.println(threadLocal.get()); fun22(); } private void fun22(){ System.out.println(threadLocal.get()); fun33(); } private void fun33(){ int age = threadLocal.get(); System.out.println(age); } public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); ThreadLocalDemo2 demo = new ThreadLocalDemo2(); for ( int i = 0 ; i < 30 ; i++) { final int j = i; service.execute(()->{ try { threadLocal.set(j); demo.fun11(); } finally { threadLocal.remove(); } }); } } } Copy code

As you can see, here we no longer pass the age parameter back and forth, but create a copy of age for each thread

This way all methods can access the copy, while also ensuring thread safety

However, it should be noted that this use is different from the last time. This time there is a remove method. Its function is to delete the copy of the above set. This will be introduced below.

3. Internal Principles

First is that it is how to do data isolation of

Let's first look at the set method:

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 code

As you can see, the value is stored in the map (key is a ThreadLocal object, and value is a copy created separately for the thread)

And how did this map come from? Let's look at the following code

ThreadLocalMap getMap (Thread t) { return t.threadLocals; } Copy code

As you can see, I finally returned to the Thread. This is why the threads are isolated, and the threads are shared (because it is an attribute within the thread, only the current thread is visible)

Let's look at the get() method again, as follows:

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(); } Copy code

As you can see, first find the map in the current thread, and then take out the value according to the key

The setInitialValue in the last line is the initialization action to be re-executed when get is empty

Why use ThreadLocal as the Key , but it is not thread id

Is to store multiple variables

If the thread id is used as the key, then a thread in the map can only store one variable

Using ThreadLocal as the key, you can store multiple variables in one thread (by creating multiple ThreadLocal)

As follows:

private static ThreadLocal<Integer> threadLocal1 = new ThreadLocal<Integer>(); private static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<Integer>(); public void test () { threadLocal1.set( 1 ); threadLocal2.set( 2 ); System.out.println(threadLocal1.get()); System.out.println(threadLocal2.get()); } Copy code

Let's talk about its memory leak problem

Let's first look at the internal code of ThreadLocalMap:

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; } } } Copy code

It can be seen that the internal node Entry inherits the weak reference (during garbage collection, if an object has only a weak reference, it will be recycled), and then the key is set to a weak reference through super(k) in the constructor

Therefore, during garbage collection, if there is no external strong reference to ThreadLocal, then the key will be recycled directly

At this time, the key=null, and the value is still there, but it cannot be retrieved. Over time, problems will occur

The solution is to remove, by removing in finally, the copy is deleted from ThreadLocal, at this time both key and value are deleted

summary

  1. ThreadLocal is literally translated as a thread local variable. Its function is to ensure data isolation between threads and simplify parameter transfer between methods by creating a copy for each thread separately
  2. The essence of data isolation: Thread holds the ThreadLocalMap object inside, and the created copy exists here, so isolation is achieved between each thread
  3. Memory leak problem: Because the key in the ThreadLocalMap is a weak reference, during garbage collection, if the object pointed to by the key does not have a strong reference, it will be recycled. At this time, the value still exists, but it can t be retrieved. It takes a long time. There is a problem (of course if the thread exits, the value will still be recycled)
  4. Use scenarios: interviews and other occasions

Reference content:

postscript

In fact, there is no in-depth analysis of the source code part of the knowledge, mainly because of the limited energy and ability, let's go deeper later