ThreadLocal basic usage and memory leak analysis

ThreadLocal basic part

Role of threadload

Save the independent variables of the thread, that is, one for each thread. This variable works in the life cycle of the thread, reducing the trouble of passing public variables between multiple functions in the same thread.

Usage scenario

When you need to save different information for different threads.

Basic use

public class TestThreadLocal {

    private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>();
//    private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
//
//        @Override
//        protected Integer initialValue() {
//            return 0;
//        }
//    };




    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(threadLocal.get());
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(3);
                System.out.println("t2:"+threadLocal.get());
            }
        });

        t1.start();
        t2.start();
        System.out.println(threadLocal.get());

    }
}

If you need to set the default value, you can implement the initialValue method.

Typical scenario 1: we know that the object of SimpleDateFormat will be thread unsafe if it is used by multiple threads. The specific codes are as follows:

public class TestThreadLocal {


    public static ExecutorService executorService = Executors.newFixedThreadPool(16);

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


    public static void main(String[] args) throws InterruptedException {

       for (int i=0;i<1000;i++){
           executorService.submit(new Runnable() {
               @Override
               public void run() {
                   String format = simpleDateFormat.format(new Date());
                   try {
                       Date parse = simpleDateFormat.parse("2021-09-01 00:00:00");
                   } catch (ParseException e) {
                       e.printStackTrace();
                   }
                   System.out.println(format);
               }
           });

       }
       Thread.sleep(3000);
        executorService.shutdownNow();

    }
}

The operation results are as follows:

It can be seen that an exception has occurred.

Method 1: we can change to new a new SimpleDateFormat object every time, so it is no problem to run again. But some resources are wasted.

Method 2: use ThreadLocal to solve the problem. Assuming that there are 16 threads in the thread pool, we have a total of 16 SimpleDateFormat objects that can handle all date formatting calls.

The code is as follows:

public class TestThreadLocal {


    public static ExecutorService executorService = Executors.newFixedThreadPool(16);


    private static ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<SimpleDateFormat>(){

        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };


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


    public static void main(String[] args) throws InterruptedException {

       for (int i=0;i<1000;i++){
           executorService.submit(new Runnable() {
               @Override
               public void run() {
                   String format = threadLocal.get().format(new Date());
                   try {
                       Date parse = threadLocal.get().parse("2021-09-01 00:00:00");
                   } catch (ParseException e) {
                       e.printStackTrace();
                   }
                   System.out.println(format);
               }
           });

       }
       Thread.sleep(3000);
        executorService.shutdownNow();

    }
}

be careful:   If the thread pool is not used, the threadLocalMap in the thread will also be recycled when the thread ends. However, if the thread pool is used, the threads in the thread pool will be reused, and the threadLocalMap in the thread will not be recycled, resulting in memory leakage. According to the correct use method, remove should be used up every time, but the efficiency is very low. It's better for method 1 to new a new SimpleDateFormat object each time. (but I think it's actually OK. It doesn't matter at all. However, threadlocal is not specifically used to solve thread safety problems, so it's not recommended.)

Correct use method

  • Every time ThreadLocal is used, its remove() method is called to clear the data
  • Define the ThreadLocal variable as private static, so that there will always be a strong reference to ThreadLocal, which can ensure that the value value of the Entry can be accessed through the weak reference of ThreadLocal at any time, and then cleared.

ThreadLocal advanced section

Why does ThreadLocal leak memory?

Memory Leak refers to the heap memory that has been dynamically allocated in the program. For some reason, the program does not release or cannot release, resulting in a waste of system memory, slowing down the running speed of the program and even system crash.

Imagine:

A thread corresponds to a piece of working memory, and a thread can store multiple ThreadLocal. Suppose that 10000 threads are started and 10000 threadlocales are created for each thread, that is, 10000 threadlocales are maintained for each thread. After the thread execution is completed, assuming that the entries in these threadlocales will not be recycled, it will easily lead to heap memory overflow.

What should I do? Doesn't the JVM provide a solution?

answer:

  1. The JVM uses setting the Key of ThreadLocalMap as a weak reference to avoid memory leakage.
  2. When the JVM calls the remove, get and set methods, it collects the dirty value by the way.

The relationship diagram of ThreadLocal is as follows:

A ThreadLocalMap is maintained in the thread. The key in the map is a weakly referenced readLocal instance. Value is the value we set in. When the treadLocal instance object is set to null, there is no strong reference to the threadLocal instance, so theadLocal will be recycled by gc. However, our value will not be recycled because there is a strong reference connected by thread. Only when the thread ends, the strong reference is broken, and all maps, values, etc. will be recycled.

As shown below:

However, we often use thread pools. In order to reuse threads, the thread life cycle does not end, so it cannot be recycled, resulting in memory leakage.


There is a road to the mountain of books. Diligence is the path. There is no end to learning. It is hard to make a boat

Keywords: Java

Added by ofaltins on Wed, 29 Sep 2021 06:21:25 +0300