This series of articles have been supplemented and improved, and has been revised and organized into a book, The Logic of Java Programming (written by Ma Junchang), published by Huazhang Branch of China Machine Press, which was on the market in January 2018 and received high praise from readers. Major online shops and bookstores are available for sale, welcome to buy: JINGdong self-run link

In this section, we’ll explore a particular concept, thread-local variables, implemented in Java as class ThreadLocal. What is that? What’s the use? What is the implementation principle? Let’s talk about it step by step.

Basic concepts and usage

Thread-local variables mean that each thread has a unique copy of the same variable. This concept may sound a bit confusing, but let’s first look at the use of the class TheadLocal.

ThreadLocal is a generic class that takes a type parameter T, has only an empty constructor, and has two main public methods:

public T get(a)
public void set(T value)
Copy the code

If there is no value, null is returned. ThreadLocal looks like a container of single objects, such as:

public static void main(String[] args) {
    ThreadLocal<Integer> local = new ThreadLocal<>();
    local.set(100);
    System.out.println(local.get());
}
Copy the code

The output is 100.

So what’s so special about ThreadLocal? This happens when there are multiple threads. Here’s an example:

public class ThreadLocalBasic {
    static ThreadLocal<Integer> local = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        Thread child = new Thread() {
            @Override
            public void run(a) {
                System.out.println("child thread initial: " + local.get());
                local.set(200);
                System.out.println("child thread final: "+ local.get()); }}; local.set(100);
        child.start();
        child.join();
        System.out.println("main thread final: "+ local.get()); }}Copy the code

Local is a static variable. The main method creates a child thread called child. Both main and child call local.

child thread initial: null
child thread final: 200
main thread final: 100
Copy the code

The local variable is set by the main thread, and the local variable is set by the Child thread. The local variable is set by the main thread, and the local variable is set by the Child thread, and the local variable is set by the main thread.

In addition to get/set, ThreadLocal has two other methods:

protected T initialValue(a)
public void remove(a)
Copy the code

InitialValue is used to provide an initialValue. It is a protected method that can be provided as an anonymous inner class. When the get method is called, it is called to get the initialValue if it has not been set before. Remove deletes the value corresponding to the current thread. If get is called again after deletion, initialValue will be called again to obtain the initialValue. Here’s a simple example:

public class ThreadLocalInit {
    static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){

        @Override
        protected Integer initialValue(a) {
            return 100; }};public static void main(String[] args) {
        System.out.println(local.get());
        local.set(200); local.remove(); System.out.println(local.get()); }}Copy the code

The output value is 100.

Usage scenarios

What does ThreadLocal do? Let’s look at a couple of examples.

DateFormat/SimpleDateFormat

ThreadLocal is to implement a scheme of thread safety, for example the DateFormat/SimpleDateFormat, date and time we spent in the 32 section operation, mentioned that they are thread-safe, achieve safe one way is to use a lock, and another way is to create a new object each time, A better approach would be to use ThreadLocal, where each thread uses its own DateFormat, and there is no safety issue. It only needs to be created once for the entire lifetime of the thread, avoiding the overhead of frequent creation.

public class ThreadLocalDateFormat {
    static ThreadLocal<DateFormat> sdf = new ThreadLocal<DateFormat>() {

        @Override
        protected DateFormat initialValue(a) {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }};public static String date2String(Date date) {
        return sdf.get().format(date);
    }

    public static Date string2Date(String str) throws ParseException {
        returnsdf.get().parse(str); }}Copy the code

It should be noted that ThreadLocal objects are generally defined as static for easy reference.

ThreadLocalRandom

For example, we introduced the Random class in Section 34. Random is thread-safe, but performance degrades if concurrent access is competitive. So Java packages ThreadLocalRandom, It is a subclass of Random that utilizes ThreadLocal. It has no constructor for public and uses the static method current to get objects, such as:

public static void main(String[] args) {
    ThreadLocalRandom rnd = ThreadLocalRandom.current();
    System.out.println(rnd.nextInt());
}
Copy the code

The current method is implemented as follows:

public static ThreadLocalRandom current(a) {
    return localRandom.get();
}
Copy the code

LocalRandom is a ThreadLocal variable:

private static final ThreadLocal<ThreadLocalRandom> localRandom =
    new ThreadLocal<ThreadLocalRandom>() {
        protected ThreadLocalRandom initialValue(a) {
            return newThreadLocalRandom(); }};Copy the code

Context information

ThreadLocal typical use is to provide the context information, such as in a Web server, a thread to perform the user’s request, in the process of execution, a lot of code will visit some common information, such as the requested information, the user identity information, database connection, the current transaction, etc., they are thread execution in the process of global information, ThreadLocal is handy if the code is verbose when passed as an argument, so it’s used in various frameworks like Spring. Let’s look at a simple example:

public class RequestContext {
    public static class Request { / /...
    };

    private static ThreadLocal<String> localUserId = new ThreadLocal<>();
    private static ThreadLocal<Request> localRequest = new ThreadLocal<>();

    public static String getCurrentUserId(a) {
        return localUserId.get();
    }

    public static void setCurrentUserId(String userId) {
        localUserId.set(userId);
    }

    public static Request getCurrentRequest(a) {
        return localRequest.get();
    }

    public static void setCurrentRequest(Request request) { localRequest.set(request); }}Copy the code

When get the information for the first time, call set methods such as setCurrentRequest/setCurrentUserId Settings, and then you can in any other parts of the code call get relevant methods were obtained.

Basic Implementation Principles

How is ThreadLocal implemented? Why should each thread have its own value for get/set of the same object? Let’s go straight to the code.

The code for the set method is:

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

It calls getMap, with the following code:

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

Returns the thread instance variable threadLocals, which is initialized to null, at which point the set is initialized by calling createMap with the code:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

As you can see from the above code, each thread has a Map of type ThreadLocalMap. Calling set actually sets an entry in the thread’s own Map with the key being the current ThreadLocal object and the value being value. WeakReference . We didn’t mention WeakReference, which is related to Java’s garbage collection mechanism. It’s easy to recycle memory, which we won’t talk about.

The code for the get method is:

public T get(a) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null)
            return (T)e.value;
    }
    return setInitialValue();
}
Copy the code

Access the Map through the thread, use the ThreadLocal object as the key to get the entry from the Map, take its value, if not in the Map, call setInitialValue, the code is:

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

InitialValue () is the method mentioned earlier that provides an initialValue. The default implementation is to return null.

The code for the remove method is also straightforward, as follows:

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

To sum up, each thread has a Map. For each ThreadLocal object, a call to get/set essentially reads and writes the Map of the current thread using the ThreadLocal object as the key. In this way, each thread has its own copy.

Thread pools and ThreadLocal

We talked about thread pools in Section 78. We know that threads in a thread pool are reusable. What happens if an asynchronous task uses a ThreadLocal? Perhaps unexpectedly, let’s look at a simple example:

public class ThreadPoolProblem {
    static ThreadLocal<AtomicInteger> sequencer = new ThreadLocal<AtomicInteger>() {

        @Override
        protected AtomicInteger initialValue(a) {
            return new AtomicInteger(0); }};static class Task implements Runnable {

        @Override
        public void run(a) {
            AtomicInteger s = sequencer.get();
            int initial = s.getAndIncrement();
            // Expect to start with 0System.out.println(initial); }}public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.execute(new Task());
        executor.execute(new Task());
        executor.execute(newTask()); executor.shutdown(); }}Copy the code

For the asynchronous Task Task, it should always expect an initial value of 0, but running the program results in:

0
0
1
Copy the code

The third time the asynchronous task is executed, the result is not correct. Why? Because threads in the thread pool do not empty the ThreadLocal object when they execute the next task after completing one task, the modified value is carried to the next asynchronous task. So what to do? There are several ideas:

  1. The first time you use a ThreadLocal object, you always call set to set the initialValue, or remove if ThreaLocal overrides the initialValue method
  2. After using a ThreadLocal object, its remove method is always called
  3. Use custom thread pools

For the first, add the set or remove code at the start of the Task’s run method, as follows:

static class Task implements Runnable {

    @Override
    public void run(a) {
        sequencer.set(new AtomicInteger(0));
        / / or sequencer. Remove ();
        
        AtomicInteger s = sequencer.get();
        / /...}}Copy the code

For the second, wrap Task’s run method ina try/finally statement and call remove in the finally statement as follows:

static class Task implements Runnable {

    @Override
    public void run(a) {
        try{
            AtomicInteger s = sequencer.get();
            int initial = s.getAndIncrement();
            // Expect to start with 0
            System.out.println(initial);    
        }finally{ sequencer.remove(); }}}Copy the code

The other option is to extend the thread pool ThreadPoolExecutor, which has a method that can be extended:

protected void beforeExecute(Thread t, Runnable r) {}Copy the code

BeforeExecure is executed in thread T before the thread pool hands off task R to thread T, and ThreadLocal can be reinitialized in this method. If you know all the ThreadLocal variables that need to be initialized, you can initialize them explicitly. If you don’t, you can reset all ThreadLocal variables by reflection, which we’ll cover in more detail in a later section.

Create a custom thread pool MyThreadPool with the following code:

static class MyThreadPool extends ThreadPoolExecutor {
    public MyThreadPool(int corePoolSize, int maximumPoolSize,
            long keepAliveTime, TimeUnit unit,
            BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        try {
            // Use reflection to clear all ThreadLocal
            Field f = t.getClass().getDeclaredField("threadLocals");
            f.setAccessible(true);
            f.set(t, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.beforeExecute(t, r); }}Copy the code

Here, reflection is used to find the Map variable threadLocals in the thread that stores the ThreadLocal object and reset to NULL. Example code for MyThreadPool is as follows:

public static void main(String[] args) {
    ExecutorService executor = new MyThreadPool(2.2.0,
            TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
    executor.execute(new Task());
    executor.execute(new Task());
    executor.execute(new Task());
    executor.shutdown();
}
Copy the code

Using any of the solutions described above, the results are as expected.

summary

This section introduces the basic concepts, usage, implementation principles, and considerations of ThreadLocal in conjunction with thread pools.

  • ThreadLocal allows each thread to have its own independent copy of the same variable, which is one way to achieve thread-safety and reduce contention.
  • ThreadLocal is often used to store context information, avoiding passing it back and forth between different codes and simplifying code.
  • Each thread has a Map, and a get/set call to a ThreadLocal object actually reads or writes the Map of the current thread with the ThreadLocal object as the key.
  • When using ThreadLocal in a thread pool, care needs to be taken to ensure that the initial values are as expected.

We’ve been talking about concurrency since Section 65, and that’s pretty much the end of it. In the next section, let’s briefly review and summarize.

(As with the other sections, all code in this section is located at github.com/swiftma/pro… In addition, as in previous chapters, this section is based on Java 7, with some changes to Java 8. We will introduce updates to Java 8 in subsequent chapters.)


To be continued, check the latest articles, please pay attention to the wechat public account “Lao Ma said programming” (scan the qr code below), from entry to advanced, simple, Lao Ma and you explore the nature of Java programming and computer technology. All rights reserved.