This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!

Let’s pick up where we left off with another classic usage scenario for ThreadLocal

Link from the previous article: Classic Usage scenarios for ThreadLocal

Classic scene

ThreadLocal is used as a per-thread exclusive object, creating a copy for each thread so that each thread can modify its own copy without affecting other threads’ copies, ensuring thread safety.

A few days ago I saw an introduction on the Internet, written really good, let’s study together.

Experiment with simpleData Format

Test with 10 threads

Suppose we have 10 threads corresponding to 10 SimpleDateFormat objects. To see what the output is, let’s write it like this:

public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { int finalI = i; new Thread(() -> { String date = new ThreadLocalDemo02().date(finalI); System.out.println(date); }).start(); Thread.sleep(100); } } public String date(int seconds) { Date date = new Date(1000 * seconds); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); return simpleDateFormat.format(date); }}Copy the code

The code above uses a for loop to fulfill this requirement. The for loop loops 10 times, each time creating a new thread and each thread creating a SimpleDateFormat object in the date method.

You can see that there are 10 threads, corresponding to 10 SimpleDateFormat objects.

The result of the code:

The requirement becomes that 1000 threads use SimpleDateFormat

But you can’t create threads all the time, because the more threads you have, the more resources you use. Let’s say we need 1000 tasks. Instead of using for loops, we should use thread pools to reuse threads, otherwise it will consume too much memory and other resources.

Using thread pools:

public static ExecutorService threadPool = Executors.newFixedThreadPool(16); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadPool.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalDemo03().date(finalI); System.out.println(date); }}); } threadPool.shutdown(); } public String date(int seconds) { Date date = new Date(1000 * seconds); SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss"); return dateFormat.format(date); }}Copy the code

As you can see, we used a pool of 16 threads and submitted 1000 tasks to that pool. In each task, it does the same thing as before, executing the date method and creating a simpleDateFormat object in that method. One result of a program (multithreaded, not unique) :

00:00 00:07 00:04 00:02... 16:29 16:28 16:27 16:26 16:39Copy the code

The program runs correctly, printing out 1000 times from 00:00 to 16:39, and there is no duplicate time. Let’s graphically represent this code, as shown in the figure:

On the left is a thread pool and on the right is 1000 tasks. What we’ve just done is we’ve created one simpleDateFormat object per task, which means there are 1,000 simpleDateFormat objects for every 1,000 tasks.

This is not necessary, however, because there is an overhead to create so many objects, an overhead to destroy them after use, and a waste of memory to have so many objects in memory at the same time.

Now let’s optimize it. Since you don’t want so many simpleDateFormat objects, it’s easiest to just use one

All threads share a single simpleDateFormat object

Let’s use the following code to demonstrate using a Single simpleDateFormat object:

public static ExecutorService threadPool = Executors.newFixedThreadPool(16); static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss"); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadPool.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalDemo04().date(finalI); System.out.println(date); }}); } threadPool.shutdown(); } public String date(int seconds) { Date date = new Date(1000 * seconds); return dateFormat.format(date); }}Copy the code

In the code, you can see that nothing else has changed, but what has changed is that we’ve extracted this simpleDateFormat object, made it a static variable, and when we need to use it, we can just fetch that static object. It looks like we’ve omitted the overhead of creating 1000 simpleDateFormat objects, so it looks fine, so let’s graphically represent this:

As you can see from the figure, we have different threads, and the threads perform their tasks. But the different tasks all call the same simpleDateFormat object, so they all point to the same object, but that makes it thread-unsafe.

4 The thread is not safe. The concurrency security problem occurs

00:04 00:04 00:05 00:04... Now not, respectCopy the code

Executing the above code reveals that what the console prints is inconsistent with what we expect. What we expect is that the print time is not repeated, but you can see that there is a repetition here, for example the first line and the second line are both 04 seconds, which means that something has gone wrong internally.

lock

The reason for the error is that the simpleDateFormat object itself is not a thread-safe object and should not be accessed by multiple threads simultaneously. So we came up with a solution: synchronized. The code is then modified to look like this:

public static ExecutorService threadPool = Executors.newFixedThreadPool(16); static SimpleDateFormat dateFormat = new SimpleDateFormat("mm:ss"); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadPool.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalDemo05().date(finalI); System.out.println(date); }}); } threadPool.shutdown(); } public String date(int seconds) { Date date = new Date(1000 * seconds); String s = null; synchronized (ThreadLocalDemo05.class) { s = dateFormat.format(date); } return s; }}Copy the code

You can see that the synchronized keyword was added to the date method, locking the call to simpleDateFormat.

00:00 00:01onsaturday (UK time) 00:06... 15:56 scatter blessedCopy the code

Such results were normal, and there was no time for repetition. But with the use of the synchronized keyword, we get into a queued state where multiple threads can’t work at the same time, and overall efficiency is greatly reduced. Is there a better solution?

What we want to achieve is that we don’t waste too much memory, but at the same time we want to be thread safe. On reflection, you could do this by having each thread have its own simpleDateFormat object, which would give you the best of both worlds.

Name the main character, ThreadLocal

So, to do that, we can use a ThreadLocal. The sample code looks like this:

public class ThreadLocalDemo06 { public static ExecutorService threadPool = Executors.newFixedThreadPool(16); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadPool.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalDemo06().date(finalI); System.out.println(date); }}); } threadPool.shutdown(); } public String date(int seconds) { Date date = new Date(1000 * seconds); SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get(); return dateFormat.format(date); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("mm:ss"); }}; }Copy the code

In this code, we use a ThreadLocal to help each thread generate its own simpleDateFormat object, which is unique to each thread. But at the same time, this object is not going to be created too much, only 16 in total, because there are only 16 threads.

00:05 00:04 00:01onsaturday (UK time)... Scatter the blessed roar,Copy the code

This result is correct, there is no repetition of time.

Let’s take a look at the current state of affairs:

As you can see on the left side of the figure, there are 16 threads in the thread pool, corresponding to 16 simpleDateFormat objects. And on the right side of the picture, you have 1,000 tasks, and there are a lot of tasks, there are 1,000 tasks as before. But the big change here is that we no longer need to create 1000 simpleDateFormat objects, even though there are 1000 tasks. Even with more tasks, you’ll end up with the same number of simpleDateFormat objects as the number of threads. This is an efficient use of memory while ensuring thread safety.

This is the first very typical scenario where ThreadLocal is appropriate.

Thank you for reading, like, comment and share

overtones

Learn endlessly, a little every day. Share a little more!