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 multithreadingSimpleDateFormat
Time formatting is thread unsafe.
2. Solutions
There are five solutions for SimpleDateFormat thread unsafe:
- will
SimpleDateFormat
Defined as a local variable; - use
synchronized
Lock execution; - use
Lock
Lock execution (similar to solution 2); - use
ThreadLocal
; - use
JDK 8
Provided 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, willSimpleDateFormat
Once 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 tosynchronized
It’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
.ThreadLocal
Thread-local variable is a thread local variableThreadLocal
This 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 itThreadLocal
The 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,ThreadLocal
Is not equal to a local variable, where “local variable” means a local variable like in the 2.1 example code,ThreadLocal
The biggest difference with local variables is:ThreadLocal
A private variable that belongs to a thread, if a thread pool is usedThreadLocal
The difference between code level local variables and code level local variables is shown in the following figure:More aboutThreadLocal
You 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:
- Thread 1 executes
calendar.setTime(date)
Method to convert the time entered by the user to the time required for subsequent formatting; - Thread 1 suspends execution and thread 2 gets
CPU
The time slice starts executing; - Thread 2 executes
calendar.setTime(date)
Method, the time is modified; - Thread 2 suspends execution and thread 1 concludes
CPU
The 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 1date1
And thread 2date2
Because of the order of execution, are eventually formatted intodate2 formatted
Instead of thread 1date1 formatted
And 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.