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

1. What is thread unsafe?

Thread unsafe, also known as non-thread safe, refers to a situation in which a program’s execution results do not match the expected results of a multithreaded execution.

Thread unsafe code

SimpleDateFormat is a typical example of thread insecurity, so let’s implement it. First we create 10 threads to format the time. Each time the time format is passed, the time to format is different, so the program will print 10 different values if executed correctly.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleDateFormatExample {
    // Create the SimpleDateFormat object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        // Create a thread pool
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        // Execute time formatting 10 times
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // The thread pool executes the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Create a time object
                    Date date = new Date(finalI * 1000);
                    // Execute the time format and print the resultSystem.out.println(simpleDateFormat.format(date)); }}); }}}Copy the code

We expect the correct result to look like this (different values for 10 prints) :However, the above program runs like this:As you can see from the above results, when used in multithreadingSimpleDateFormatTime formatting is thread unsafe.

2. Solutions

There are five solutions for SimpleDateFormat thread unsafe:

  1. willSimpleDateFormatDefined as a local variable;
  2. usesynchronizedLock execution;
  3. useLockLock execution (similar to solution 2);
  4. useThreadLocal;
  5. useJDK 8Provided in theDateTimeFormat.

Let’s look at the implementation of each solution.

Change SimpleDateFormat to a local variable

When you define SimpleDateFormat as a local variable, because each thread is unique to the SimpleDateFormat object, it is equivalent to turning a multithreaded program into a “single-threaded” program, so there is no thread unsafe problem. The implementation code is as follows:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleDateFormatExample {
    public static void main(String[] args) {
        // Create a thread pool
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        // Execute time formatting 10 times
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // The thread pool executes the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Create the SimpleDateFormat object
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
                    // Create a time object
                    Date date = new Date(finalI * 1000);
                    // Execute the time format and print the resultSystem.out.println(simpleDateFormat.format(date)); }}); }// Close the thread pool after the task is finishedthreadPool.shutdown(); }}Copy the code

The execution results of the above procedures are:When the printed results are not the same, the program execution is correct, as can be seen from the above results, willSimpleDateFormatOnce defined as a local variable, the thread insecurity problem can be successfully resolved.

② Synchronized

Synchronized = synchronized = synchronized = synchronized = synchronized = synchronized = synchronized

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleDateFormatExample2 {
    // Create the SimpleDateFormat object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        // Create a thread pool
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        // Execute time formatting 10 times
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // The thread pool executes the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Create a time object
                    Date date = new Date(finalI * 1000);
                    // Define the result of formatting
                    String result = null;
                    synchronized (simpleDateFormat) {
                        // Time formatting
                        result = simpleDateFormat.format(date);
                    }
                    // Print the resultSystem.out.println(result); }}); }// Close the thread pool after the task is finishedthreadPool.shutdown(); }}Copy the code

The execution results of the above procedures are:

Use Lock to Lock

In the Java language, there are two common ways to implement locks. In addition to synchronized, we can also use a manual Lock. Then we can use Lock to modify thread-unsafe code.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/** * Lock to resolve thread insecurity */
public class SimpleDateFormatExample3 {
    // Create the SimpleDateFormat object
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    public static void main(String[] args) {
        // Create a thread pool
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        // Create a Lock
        Lock lock = new ReentrantLock();
        // Execute time formatting 10 times
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // The thread pool executes the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Create a time object
                    Date date = new Date(finalI * 1000);
                    // Define the result of formatting
                    String result = null;
                    / / lock
                    lock.lock();
                    try {
                        // Time formatting
                        result = simpleDateFormat.format(date);
                    } finally {
                        / / releases the lock
                        lock.unlock();
                    }
                    // Print the resultSystem.out.println(result); }}); }// Close the thread pool after the task is finishedthreadPool.shutdown(); }}Copy the code

The execution results of the above procedures are:As you can see from the above code, manual locking is written in comparison tosynchronizedIt’s a little more complicated.

(4) using ThreadLocal

Although the locking scheme can correctly solve the problem of thread insecurity, it also introduces new problems. Locking will make the program enter the queued execution process, thus reducing the execution efficiency of the program to a certain extent, as shown in the figure below:Is there a way to solve the thread insecurity problem while avoiding queueing?

The answer is yes, consider using itThreadLocal.ThreadLocalThread-local variable is a thread local variableThreadLocalThis is used to create private (local) variables for threads. Each thread has its own private object. This avoids thread insecurity.Now that we know the implementation, let’s use the concrete code to demonstrate itThreadLocalThe implementation code is as follows:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/** * Thread safety */
public class SimpleDateFormatExample4 {
    // Create a ThreadLocal object and set the default value (new SimpleDateFormat)
    private static ThreadLocal<SimpleDateFormat> threadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));

    public static void main(String[] args) {
        // Create a thread pool
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        // Execute time formatting 10 times
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // The thread pool executes the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Create a time object
                    Date date = new Date(finalI * 1000);
                    // Format time
                    String result = threadLocal.get().format(date);
                    // Print the resultSystem.out.println(result); }}); }// Close the thread pool after the task is finishedthreadPool.shutdown(); }}Copy the code

The execution results of the above procedures are:

The difference between ThreadLocal and local variables

First of all,ThreadLocalIs not equal to a local variable, where “local variable” means a local variable like in the 2.1 example code,ThreadLocalThe biggest difference with local variables is:ThreadLocalA private variable that belongs to a thread, if a thread pool is usedThreadLocalThe difference between code level local variables and code level local variables is shown in the following figure:More aboutThreadLocalYou can visit lei’s previous articleIf ThreadLocal doesn’t work, you’re not using it..

(5) use DateTimeFormatter

All four of the above solutions are because SimpleDateFormat is thread unsafe, so we need to lock it or use ThreadLocal to handle it. However, after JDK 8, we have a new option. If we use JDK 8+, Use the DateTimeFormatter class to format the time. Use the DateTimeFormatter class to format the time.

DateTimeFormatter must be used in conjunction with LocalDateTime, a new time object in JDK 8. Therefore, we can convert the Date object to LocalDateTime first. The DateTimeFormatter is used to format the time.

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/** * DateTimeFormatter */
public class SimpleDateFormatExample5 {
    // Create the DateTimeFormatter object
    private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("mm:ss");

    public static void main(String[] args) {
        // Create a thread pool
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        // Execute time formatting 10 times
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            // The thread pool executes the task
            threadPool.execute(new Runnable() {
                @Override
                public void run(a) {
                    // Create a time object
                    Date date = new Date(finalI * 1000);
                    // Convert Date to JDK 8 time type LocalDateTime
                    LocalDateTime localDateTime =
                            LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
                    // Time formatting
                    String result = dateTimeFormatter.format(localDateTime);
                    // Print the resultSystem.out.println(result); }}); }// Close the thread pool after the task is finishedthreadPool.shutdown(); }}Copy the code

The execution results of the above procedures are:

3. Cause analysis of thread insecurity

Want to know why SimpleDateFormat is thread-unsafe? We need to look at and analyze the source code of SimpleDateFormat, so let’s start with the format method. The 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

Perhaps it was a stroke of luck that I found the thread unsafe problem when I started analyzing the first method.

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:In the case of multithreaded execution, thread 1date1And thread 2date2Because of the order of execution, are eventually formatted intodate2 formattedInstead of thread 1date1 formattedAnd thread 2date2 formatted, which can lead to thread insecurity.

4. Summary of advantages and disadvantages of each plan

If you’re using JDK 8+, you can use the thread-safe DateTimeFormatter directly. If you’re using JDK 8 or later, or if you’re modifying the old SimpleDateFormat code, Consider using synchronized or ThreadLocal to solve the thread insecurity problem. Because implementing the solution for local variables in Scenario 1 creates a new object each time it is executed, it is not recommended. Synchronized has a simpler implementation, and using ThreadLocal avoids the problem of locking queued execution.

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