A small example of how to use ThreadLocal in seconds

Problems encountered

Recently, a small partner had a problem using the date tool class encapsulated in the project in a multithreaded environment. Let's see what happened

A tool class for date conversion

public class DateUtil {

    private static final SimpleDateFormat sdf = 
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static Date parse(String dateStr) {
        Date date = null;
        try {
            date = sdf.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

Then use this tool class in multithreaded environment

public static void main(String[] args) {

    ExecutorService service = Executors.newFixedThreadPool(20);

    for (int i = 0; i < 20; i++) {
        service.execute(()->{
            System.out.println(DateUtil.parse("2019-06-01 16:34:30"));
        });
    }
    service.shutdown();
}

It turned out to be abnormal

This exception is not analyzed from the source point of view. Write a small Demo, understand the small Demo, and understand the reason

A tool class for adding numbers to 10

 

public class NumUtil {

    public static int addNum = 0;

    public static int add10(int num) {
        addNum = num;
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return addNum + 10;
    }
}
public static void main(String[] args) {

    ExecutorService service = Executors.newFixedThreadPool(20);

    for (int i = 0; i < 20; i++) {
        int num = i;
        service.execute(()->{
            System.out.println(num + " " +  NumUtil.add10(num));
        });
    }
    service.shutdown();
}

Part of the code is then output as

0 28
3 28
7 28
11 28
15 28

What the hell, didn't you add 10? Why did you output 28? This is mainly because of the thread switching. The thread successively sets the addNum value to 0, 3 and 7, but it is switched before the execution is finished (it is not executed to the return addNum+10 step). When one of the threads sets the addNum value to 18, the thread successively starts to execute the addNum+10 step, and the result is output 28.

The reason for SimpleDateFormat is similar to this. It is mainly because the shared variable 'public static int addNum = 0;', 'private static final SimpleDateFormat sdf = new SimpleDateFormat ("yyyy MM DD HH: mm: SS");' defined within the class is shared when multiple threads are used.

So how can we solve this problem?

Solution

Solution 1: every time I come here, I'm new and waste a lot of space

public class DateUtil {

    public static Date parse(String dateStr) {
        SimpleDateFormat sdf =
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = null;
        try {
            date = sdf.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

Solution 2: the method is decorated with synchronized, and the concurrency cannot be improved

public class DateUtil {

    private static final SimpleDateFormat sdf =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static synchronized Date parse(String dateStr) {
        Date date = null;
        try {
            date = sdf.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

Solution 3: use the date format classes DateFormatter and DateTimeFormatter in jdk1.8, and do not want to change the code solution of this tool class used in the project

4: With ThreadLocal, one thread and one SimpleDateFormat object

public class DateUtil {

    private static ThreadLocal<DateFormat> threadLocal = ThreadLocal.withInitial(
            ()-> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static Date parse(String dateStr) {
        Date date = null;
        try {
            date = threadLocal.get().parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

The above tool class plus 10 can be changed to the following form (mainly to demonstrate the use of ThreadLocal)

public class NumUtil {

    private static ThreadLocal<Integer> addNumThreadLocal = new ThreadLocal<>();

    public static int add10(int num) {
        addNumThreadLocal.set(num);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return addNumThreadLocal.get() + 10;
    }
}

Now the two tool classes can be used normally. Why?

Principle analysis

When multiple threads read and write the same shared variable at the same time, there is a concurrency problem. If they do not share, there will be no concurrency problem. A Thread stores a variable of its own. Compared with several people playing the same ball, now there is no problem with one person playing the same ball. How to store the variable on the Thread? In fact, there is already a Map container inside the Thread class to store variables. Its approximate structure is as follows

 

ThreadLocalMap is a Map, key is ThreadLocal, value is Object

Let's look at the following article in detail: https://www.jianshu.com/p/98b68c97df9b

Tags: Programming

Posted on Mon, 25 May 2020 03:58:31 -0400 by argh2xxx