This article is participating in the Java Topic Month – Java Debug Notes event. See the event link for details

In Java, if you want to ask which class is easy to use, but the least easy to use? I’m sure the word “ThreadLocal” comes to mind.

Indeed, ThreadLocal was originally designed to solve the problem of concurrent threads sharing variables, but due to overdesigns such as weak references and hash collisions, it is difficult to understand and expensive to use. Of course, there are still problems with dirty data, out of memory, shared variable updates, and so on, but even so, ThreadLocal has its own usage scenarios and irreplaceable value, such as the two usage scenarios described in this article. There is really no alternative to ThreadLocal.

Scenario 1: Local variables

To demonstrate the value and usefulness of ThreadLocal, let’s use the example of multithreaded formatting time, as we typically do when formatting time across multiple threads.

(1) Two-thread formatting

When we have 2 threads for time formatting, we can write:

import java.text.SimpleDateFormat;
import java.util.Date;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        // Create and start thread 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Get the time object
                Date date = new Date(1 * 1000);
                // Perform time formattingformatAndPrint(date); }}); t1.start();// Create and start thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Get the time object
                Date date = new Date(2 * 1000);
                // Perform time formattingformatAndPrint(date); }}); t2.start(); }/** * Format and print the result *@paramDate Time object */
    private static void formatAndPrint(Date date) {
        // Format the time object
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        // Perform formatting
        String result = simpleDateFormat.format(date);
        // Print the final result
        System.out.println("Time:"+ result); }}Copy the code

The execution results of the above procedures are:The above code creates a small number of threads, so we can create a private object for each threadSimpleDateFormatTo format the time.

② Format 10 threads

When the number of threads is increased from 2 to 10, we can use the for loop to create multiple threads to execute the time format as follows:

import java.text.SimpleDateFormat;
import java.util.Date;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // Create a thread
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run(a) {
                    // Get the time object
                    Date date = new Date(finalI * 1000);
                    // Perform time formattingformatAndPrint(date); }});// Start the threadthread.start(); }}/** * Format and print time *@paramDate Time object */
    private static void formatAndPrint(Date date) {
        // Format the time object
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
        // Perform formatting
        String result = simpleDateFormat.format(date);
        // Print the final result
        System.out.println("Time:"+ result); }}Copy the code

The execution results of the above procedures are:As you can see from the above results, although the number of threads created at this point andSimpleDateFormatThat’s not a small number, but it works.

③ format 1000 threads

However, when we changed the number of threads from 10 to 1000, we could not simply use the for loop to create 1000 threads, because the frequent creation and destruction of threads would cause a lot of overhead and excessive contention for CPU resources.

So after some thinking, we decided to use a thread pool to perform this task 1000 times, because the thread pool threads can reuse resources, without frequent and destroy new threads, can also be avoided by controlling the number of threads in thread pool multi-threaded * * * * CPU resources, as a result of excessive competition, and thread caused by frequent switching performance issues, And we can promote the SimpleDateFormat to a global variable to avoid the problem of creating a new SimpleDateFormat every time we execute it, so we write code like this:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class App {
    // Time format the object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) throws InterruptedException {
        // Create a thread pool to execute the task
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10.10.60,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            // Execute the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Get the time object
                    Date date = new Date(finalI * 1000);
                    // Perform time formattingformatAndPrint(date); }}); }// The thread pool is closed after executing its task
        threadPool.shutdown();
    }

    /** * Format and print time *@paramDate Time object */
    private static void formatAndPrint(Date date) {
        // Perform formatting
        String result = simpleDateFormat.format(date);
        // Print the final result
        System.out.println("Time:"+ result); }}Copy the code

The execution results of the above procedures are:When we run the program with great joy, we find that something unexpected happens, and the code is thread-safe. It can be seen from the above results that the print results of the program should have repeated content, the correct situation should be no time to repeat.

PS: The so-called thread safety problem refers to: in the execution of multiple threads, the program execution results do not match the expected results.

A) Analysis of thread safety issues

To find out what the problem is, let’s try looking at the source code of the format method in SimpleDateFormat. The format source code is as follows:

private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) {
    // Notice this code
    calendar.setTime(date);

    boolean useDateFormatSymbols = useDateFormatSymbols();

    for (int i = 0; i < compiledPattern.length; ) {
        int tag = compiledPattern[i] >>> 8;
        int count = compiledPattern[i++] & 0xff;
        if (count == 255) {
            count = compiledPattern[i++] << 16;
            count |= compiledPattern[i++];
        }

        switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break; }}return toAppendTo;
}
Copy the code

As you can see from the source code above, when executing the SimpleDateFormat.format method, the calendar. SetTime method is used to convert the input time, so imagine this scenario:

  1. Thread 1 executescalendar.setTime(date)Method to convert the time entered by the user to the time required for subsequent formatting;
  2. Thread 1 suspends execution and thread 2 getsCPUThe time slice starts executing;
  3. Thread 2 executescalendar.setTime(date)Method, the time is modified;
  4. Thread 2 suspends execution and thread 1 concludesCPUThe time slice continues because thread 1 and thread 2 are using the same object, and the time has been changed by thread 2, so thread 1 is thread safe when it continues.

Normally, the program executes like this:

The non-thread-safe execution process looks like this:

B) Solve thread-safety problems: locking

When there is a thread safety problem, the first solution we think of is locking. The implementation code is as follows:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class App {
    // Time format the object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) throws InterruptedException {
        // Create a thread pool to execute the task
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10.10.60,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            // Execute the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Get the time object
                    Date date = new Date(finalI * 1000);
                    // Perform time formattingformatAndPrint(date); }}); }// The thread pool is closed after executing its task
        threadPool.shutdown();
    }

    /** * Format and print time *@paramDate Time object */
    private static void formatAndPrint(Date date) {
        // Perform formatting
        String result = null;
        / / lock
        synchronized (App.class) {
            result = simpleDateFormat.format(date);
        }
        // Print the final result
        System.out.println("Time:"+ result); }}Copy the code

The execution results of the above procedures are:As can be seen from the above results, the use ofsynchronizedAfter adding the lock, the program can execute normally.

Disadvantages of locking

Although the locking method can solve the problem of thread safety, but also brought new problems, when the program is locked, all threads must queue to execute some business, so virtually reduce the efficiency of the program.

Is there a solution that can solve the thread-safety problem and also speed up the execution of the program?

The answer is: yes, and that’s when ThreadLocal comes into play.

C) Resolve thread-safety issues: ThreadLocal

1. The ThreadLocal is introduced

ThreadLocal is literally a thread-local variable, meaning that it is a private variable in the thread, and each thread can only use its own variable.

For example, when there are 10 threads in the thread pool, SimpleDateFormat will be stored in ThreadLocal, which will only create 10 objects, even if it takes 1000 time formatting tasks. Still, only 10 SimpleDateFormat objects will be created, each thread calling its own ThreadLocal variable.

2. Basic use of ThreadLocal

There are three core methods that ThreadLocal uses:

  1. Set method: Used to set a copy of a thread-independent variable. A ThreadLocal without a set operation is prone to dirty data.
  2. Get method: Used to get a copy of a thread-independent variable. A ThreadLocal object without a GET operation is meaningless.
  3. The remove method: removes a copy of a thread-independent variable. No remove operation is likely to cause memory leaks.

All ThreadLocal methods look like this:Official documentation:Docs.oracle.com/javase/8/do…

The basic usage of ThreadLocal is as follows:

/** * @Public account: Java Chinese Community */
public class ThreadLocalExample {
    // Create a ThreadLocal object
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // The thread executes the task
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName + "Deposit value:" + threadName);
                // Set the value in ThreadLocal
                threadLocal.set(threadName);
                // Execute the method, which prints the values set in the threadprint(threadName); }};// Create and start thread 1
        new Thread(runnable, "MyThread-1").start();
        // Create and start thread 2
        new Thread(runnable, "MyThread-2").start();
    }

    /** * Print the ThreadLocal value in the thread *@paramThreadName indicates the threadName */
    private static void print(String threadName) {
        try {
            // Get the value in ThreadLocal
            String result = threadLocal.get();
            // Print the result
            System.out.println(threadName + "Fetch value:" + result);
        } finally {
            // Remove the value from ThreadLocal (to prevent memory overflow)threadLocal.remove(); }}}Copy the code

The execution results of the above procedures are:As you can see from the above results, each thread will only read its ownThreadLocalValue.

3. Advanced usage of ThreadLocal

① Initialization: initialValue
public class ThreadLocalByInitExample {
    / / define ThreadLocal
    private static ThreadLocal<String> threadLocal = new ThreadLocal(){
        @Override
        protected String initialValue(a) {
            System.out.println("Execute the initialValue() method");
            return "Default"; }};public static void main(String[] args) {
        // The thread executes the task
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                // Execute the method that prints the data in the thread (no value is set for printing)print(threadName); }};// Create and start thread 1
        new Thread(runnable, "MyThread-1").start();
        // Create and start thread 2
        new Thread(runnable, "MyThread-2").start();
    }

    /** * Print the ThreadLocal value in the thread *@paramThreadName indicates the threadName */
    private static void print(String threadName) {
        // Get the value in ThreadLocal
        String result = threadLocal.get();
        // Print the result
        System.out.println(threadName + Get value:+ result); }}Copy the code

The execution results of the above procedures are:When using the#threadLocal.setAfter the method,initialValueThe method is not executed, as shown in the following code:

public class ThreadLocalByInitExample {
    / / define ThreadLocal
    private static ThreadLocal<String> threadLocal = new ThreadLocal() {
        @Override
        protected String initialValue(a) {
            System.out.println("Execute the initialValue() method");
            return "Default"; }};public static void main(String[] args) {
        // The thread executes the task
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                String threadName = Thread.currentThread().getName();
                System.out.println(threadName + "Deposit value:" + threadName);
                // Set the value in ThreadLocal
                threadLocal.set(threadName);
                // Execute the method, which prints the values set in the threadprint(threadName); }};// Create and start thread 1
        new Thread(runnable, "MyThread-1").start();
        // Create and start thread 2
        new Thread(runnable, "MyThread-2").start();
    }

    /** * Print the ThreadLocal value in the thread *@paramThreadName indicates the threadName */
    private static void print(String threadName) {
        try {
            // Get the value in ThreadLocal
            String result = threadLocal.get();
            // Print the result
            System.out.println(threadName + "Fetch value:" + result);
        } finally {
            // Remove the value from ThreadLocal (to prevent memory overflow)threadLocal.remove(); }}}Copy the code

The execution results of the above procedures are:

Why does the initialization code not execute after the set method?

To understand this, we need to look at the source code for the threadLocal.get () method, because initialValue is not executed immediately when a ThreadLocal is created, but only when the get method is called. The test code is as follows:

import java.util.Date;

public class ThreadLocalByInitExample {
    / / define ThreadLocal
    private static ThreadLocal<String> threadLocal = new ThreadLocal() {
        @Override
        protected String initialValue(a) {
            System.out.println("Execute the initialValue() method" + new Date());
            return "Default"; }};public static void main(String[] args) {
        // The thread executes the task
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                // Get the current thread name
                String threadName = Thread.currentThread().getName();
                // Execute the method, which prints the values set in the threadprint(threadName); }};// Create and start thread 1
        new Thread(runnable, "MyThread-1").start();
        // Create and start thread 2
        new Thread(runnable, "MyThread-2").start();
    }

    /** * Print the ThreadLocal value in the thread *@paramThreadName indicates the threadName */
    private static void print(String threadName) {
        System.out.println("Go to the print() method" + new Date());
        try {
            / / sleep 1 s
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // Get the value in ThreadLocal
        String result = threadLocal.get();
        // Print the result
        System.out.println(String.format("%s gets value: %s %s",
                threadName, result, newDate())); }}Copy the code

The execution results of the above procedures are:As can be seen from the above printing time:initialValueThe method is not inThreadLocalAt creation time, but at invocationThread.getMethod.

Let’s look at the implementation of the threadLocal. get source code:

public T get(a) {
    // Get the current thread
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // Determine whether the ThreadLocal has data
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            // Return data directly with set value
            returnresult; }}// Execute the initialization method
    return setInitialValue();
}
private T setInitialValue(a) {
    // Execute the initialization method
    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

As you can see from the source code above, the value E. value is returned directly when ThreadLocal has a value, and initialValue is executed only when ThreadLocal does not have any value.

Precautions – The type must be the same

Be careful when usinginitialValueThe type of the return value should be the sumThreadLocaThe data types defined are consistent, as shown in the figure below:If the data are inconsistent, it can causeClassCaseExceptionThe type conversion is abnormal, as shown in the following figure:

2: withInitial
import java.util.function.Supplier;

public class ThreadLocalByInitExample {
    / / define ThreadLocal
    private static ThreadLocal<String> threadLocal =
            ThreadLocal.withInitial(new Supplier<String>() {
                @Override
                public String get(a) {
                    System.out.println("Execute withInitial() method");
                    return "Default"; }});public static void main(String[] args) {
        // The thread executes the task
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                String threadName = Thread.currentThread().getName();
                // Execute the method, which prints the values set in the threadprint(threadName); }};// Create and start thread 1
        new Thread(runnable, "MyThread-1").start();
        // Create and start thread 2
        new Thread(runnable, "MyThread-2").start();
    }

    /** * Print the ThreadLocal value in the thread *@paramThreadName indicates the threadName */
    private static void print(String threadName) {
        // Get the value in ThreadLocal
        String result = threadLocal.get();
        // Print the result
        System.out.println(threadName + Get value:+ result); }}Copy the code

The execution results of the above procedures are:Through the above code,withInitialThe use of good andinitialValueIt doesn’t seem to make a difference, so why build two similar methods? Guest officer mo anxious, continue to look down.

③ more concise use withInitial

The advantage of the withInitial method is that it is easier to implement variable initialization, as shown in the following code:

public class ThreadLocalByInitExample {
    / / define ThreadLocal
    private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Default");
    public static void main(String[] args) {
        // The thread executes the task
        Runnable runnable = new Runnable() {
            @Override
            public void run(a) {
                String threadName = Thread.currentThread().getName();
                // Execute the method, which prints the values set in the threadprint(threadName); }};// Create and start thread 1
        new Thread(runnable, "MyThread-1").start();
        // Create and start thread 2
        new Thread(runnable, "MyThread-2").start();
    }

    /** * Print the ThreadLocal value in the thread *@paramThreadName indicates the threadName */
    private static void print(String threadName) {
        // Get the value in ThreadLocal
        String result = threadLocal.get();
        // Print the result
        System.out.println(threadName + Get value:+ result); }}Copy the code

The execution results of the above procedures are:

4. Time formatting with ThreadLocal

Using a ThreadLocal to format 1000 times, we will use the following code:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadLocalByDateFormat {
    // Create a ThreadLocal and set the default values
    private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));

    public static void main(String[] args) {
        // Create a thread pool to execute the task
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10.10.60,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
        // Execute the task
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            // Execute the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Get the time object
                    Date date = new Date(finalI * 1000);
                    // Perform time formattingformatAndPrint(date); }}); }// The thread pool is closed after executing its task
        threadPool.shutdown();
        // The thread pool is closed after executing its task
        threadPool.shutdown();
    }
    /** * Format and print time *@paramDate Time object */
    private static void formatAndPrint(Date date) {
        // Perform formatting
        String result = dateFormatThreadLocal.get().format(date);
        // Print the final result
        System.out.println("Time:"+ result); }}Copy the code

The execution results of the above procedures are:It can be seen from the above results that the use ofThreadLocalIt also solves the thread concurrency problem and avoids the problem of code queueing.

Usage Scenario 2: Passing data across classes

In addition to the above usage scenarios, we can also use **ThreadLocal** to pass data across classes and methods in a thread. For example, the User object information of the login User needs to be used multiple times in different subsystems. If we use the traditional method, we need to pass the User object by means of method parameters and return values. However, this will cause the coupling between classes or even between systems. So we can use ThreadLocal to pass the User object.

With the solution in place, it’s time to implement the concrete business code. We can first construct and initialize a User object in the main thread, and store the User object in a ThreadLocal. After the storage is complete, we can directly obtain and use the User object in other classes of the same thread, such as repository or order class.

Business code in the main thread:

public class ThreadLocalByUser {
    public static void main(String[] args) {
        // Initialize user information
        User user = new User("Java");
        // Store User objects in ThreadLocal
        UserStorage.setUser(user);
        // Call the order system
        OrderSystem orderSystem = new OrderSystem();
        // Add order (get user information in method)
        orderSystem.add();
        // Call the warehouse system
        RepertorySystem repertory = new RepertorySystem();
        // Reduce inventory (obtain user information in method)repertory.decrement(); }}Copy the code

User entity class:

/** * User entities */
class User {
    public User(String name) {
        this.name = name;
    }
    private String name;
    public String getName(a) {
        return name;
    }
    public void setName(String name) {
        this.name = name; }}Copy the code

ThreadLocal operation class:

/** * User information storage class */
class UserStorage {
    // User information
    public static ThreadLocal<User> USER = new ThreadLocal();

    /** * Store user information *@paramUser User data */
    public static void setUser(User user) { USER.set(user); }}Copy the code

Order type:

/** * order class */
class OrderSystem {
    /**
     * 订单添加方法
     */
    public void add(a) {
        // Get user information
        User user = UserStorage.USER.get();
        // Business process code (ignore)...
        System.out.println(String.format("Order system received a request from user: %s.", user.getName())); }}Copy the code

Storage:

/** * Storage */
class RepertorySystem {
    /** * Inventory reduction method */
    public void decrement(a) {
        // Get user information
        User user = UserStorage.USER.get();
        // Business process code (ignore)...
        System.out.println(String.format("Warehouse system received a request from user: %s.", user.getName())); }}Copy the code

Final results of the above procedures:From the above results, we can see that when we first initialize in the main threadUserObject, the order and warehouse classes can be obtained without passing any parametersUserObject, thusImplement data transfer across classes and methods in a thread.

conclusion

Using ThreadLocal does not cause thread-safety problems by creating thread-private variables, and avoids the performance cost of queueing threads for execution by introducing locks. Furthermore, using ThreadLocal enables data transfer within a thread across classes and methods.

Reference & thanks

Code Efficiently: A Java Development Manual

Concurrent Programming in Java lecture 78

Check out the Java Chinese Community for more interesting and informative concurrent programming articles.