Thread local storage mode: ThreadLocal

Topic: Thread local storage mode: no sharing, no harm

Thread closure is essentially to avoid sharing . You already know that sharing can be avoided through local variables . Is there any other way to do it? Yes, the thread local storage ( ThreadLocal) provided by the Java language can do it. Let's first look at how to use ThreadLocal.

How to use ThreadLocal

The following static class ThreadId will assign a unique thread ID to each thread. If a thread calls the get () method of ThreadId twice before and after, the return value of the two get () methods is the same. However, if two threads call the get () method of ThreadId respectively, the return value of the get () method seen by the two threads is different. If you are new to ThreadLocal, you may be wondering why the result of calling the get () method on the same thread is the same, but the result of calling the get () method on different threads is different.

static class ThreadId {
  static final AtomicLong nextId=new AtomicLong(0);
  
  //定义ThreadLocal变量
  static final ThreadLocal<Long> tl=ThreadLocal.withInitial(
    ()->nextId.getAndIncrement());
    
  //此方法会为每个线程分配一个唯一的Id
  static long get(){
    return tl.get();
  }
}

This strange result can be a masterpiece of ThreadLocal, but before explaining the working principle of ThreadLocal in detail, let's look at an example that may be encountered in actual work to deepen the understanding of ThreadLocal. You may know that SimpleDateFormat is not thread-safe, so what should you do if you need to use it in concurrent scenarios?

In fact, there is a way to solve it with ThreadLocal. The following sample code is the specific implementation of the ThreadLocal solution. This code is highly similar to the previous ThreadId code. Similarly, different threads calling SafeDateFormat's get () method will return different SimpleDateFormat Object instances, because SimpleDateFormat is not shared by different threads, are thread-safe just like local variables.

static class SafeDateFormat {
  //定义ThreadLocal变量
  static final ThreadLocal<DateFormat> tl=ThreadLocal.withInitial(
    ()-> new SimpleDateFormat(
      "yyyy-MM-dd HH:mm:ss"));
      
  static DateFormat get(){
    return tl.get();
  }
}
//不同线程执行下面代码
//返回的df是不同的
DateFormat df = SafeDateFormat.get()

Through the above two examples, I believe you have understood the usage and application scenarios of ThreadLocal. Below we will explain in detail the working principle of ThreadLocal.

How ThreadLocal works

Before explaining how ThreadLocal works, think about it yourself: if you were to implement the function of ThreadLocal, how would you design it? The goal of ThreadLocal is to let different threads have different variables V. The most direct way is to create a Map whose Key is the thread and Value is the variable V owned by each thread. ThreadLocal can hold such a Map. Too. You can refer to the following schematic diagram and sample code to understand.
Insert picture description here

class MyThreadLocal<T> {
  Map<Thread, T> locals = new ConcurrentHashMap<>();
  
  //获取线程变量  
  T get() {
    return locals.get(Thread.currentThread());
  }
  
  //设置线程变量
  void set(T t) {
    locals.put(Thread.currentThread(), t);
  }
}

Is Java's ThreadLocal implemented this way? This time our design ideas are very different from Java's implementation. There is also a Map in the Java implementation called ThreadLocalMap, but it is not ThreadLocal but Thread that holds ThreadLocalMap. The Thread class has a private attribute threadLocals, whose type is ThreadLocalMap, and the key of ThreadLocalMap is ThreadLocal. You can combine the following schematic diagram and simplified Java implementation code to understand.
Insert picture description here
Insert picture description here

class Thread {
  //内部持有ThreadLocalMap
  ThreadLocal.ThreadLocalMap  threadLocals;
}

class ThreadLocal<T>{

  public T get() {
    //首先获取线程持有的
    //ThreadLocalMap
    ThreadLocalMap map =Thread.currentThread().threadLocals;
    //在ThreadLocalMap中
    //查找变量
    Entry e = map.getEntry(this);
    return e.value;  
  }
  
  static class ThreadLocalMap{
  
    //内部是数组而不是Map
    Entry[] table;
    //根据ThreadLocal查找Entry
    Entry getEntry(ThreadLocal key){
      //省略查找逻辑
    }
    //Entry定义
    static class Entry extends WeakReference<ThreadLocal>{
      Object value;
    }
  }
}

Insert picture description here
Insert picture description here
At first glance, our design and the implementation of Java are only different from the holder of the Map. In our design, Map belongs to ThreadLocal, while in the Java implementation, ThreadLocalMap belongs to Thread. Which of these two methods is more reasonable? Obviously, the Java implementation is more reasonable. In the implementation of Java, ThreadLocal is just a proxy tool class . It does not hold any thread-related data internally. All thread-related data is stored in Thread. This design is easy to understand. In terms of data affinity, ThreadLocalMap belongs to Thread is more reasonable.

Of course, there is a deeper reason, that is, it is not easy to produce memory leaks . In our design, the Map held by ThreadLocal will hold a reference to the Thread object, which means that as long as the ThreadLocal object exists, the Thread object in the Map will never be recycled. The life cycle of ThreadLocal is often longer than the thread , so this design scheme is easy to cause memory leaks. In the implementation of Java, Thread holds ThreadLocalMap, and the reference to ThreadLocal in ThreadLocalMap is still a weak reference (WeakReference), so as long as the Thread object can be recycled, then ThreadLocalMap can be recycled. Although this implementation of Java looks more complicated, it is more secure.

Java's implementation of ThreadLocal should be considered well thought out, but even so thoughtful, it still can't let programmers avoid memory leaks 100%. For example, using ThreadLocal in a thread pool can cause memory leaks if not careful.

ThreadLocal and memory leak

Why might using ThreadLocal in the thread pool cause memory leaks? The reason is that the survival time of threads in the thread pool is too long, and they often die together with the program. This means that the ThreadLocalMap held by Thread will never be recycled, plus the Entry in ThreadLocalMap to ThreadLocal It is a weak reference (WeakReference) , so as long as ThreadLocal ends its own life cycle, it can be recycled. ** But the Value in the Entry is strongly referenced by the Entry, ** So even if the value's life cycle is over, the Value cannot be recovered, resulting in a memory leak.

In the thread pool, how do we use ThreadLocal correctly? In fact, it is very simple. Since the JVM cannot automatically release a strong reference to Value, we can release it manually. How can manual release be achieved? It is estimated that you immediately think of the try {} finally {} scheme, which is simply a weapon for manually releasing resources. The sample code is as follows, you can refer to study.

ExecutorService es;
ThreadLocal tl;

es.execute(()->{
  //ThreadLocal增加变量
  tl.set(obj);
  
  try {
    // 省略业务逻辑代码
  }finally {
    //手动清理ThreadLocal 
    tl.remove();
  }
});

InheritableThreadLocal and inheritance

Thread variables created through ThreadLocal cannot be inherited by child threads. In other words, you create a thread variable V through ThreadLocal in a thread, and then the thread creates a child thread. You cannot access the thread variable V of the parent thread through the ThreadLocal in the child thread.

What if you need the child thread to inherit the thread variable of the parent thread? In fact, it is very simple. Java provides InheritableThreadLocal to support this feature. InheritableThreadLocal is a subclass of ThreadLocal, so the usage is the same as ThreadLocal, so I wo n’t introduce it here.

However, I do not recommend that you use InheritableThreadLocal in the thread pool, not only because it has the same shortcomings as ThreadLocal-it may cause memory leaks, and the more important reason is: the creation of threads in the thread pool is dynamic, which is easy The inheritance relationship is messy. If your business logic depends on InheritableThreadLocal, it is likely to cause business logic calculation errors, which are often more fatal than memory leaks.

to sum up

The thread-local storage model is essentially a solution to avoid sharing, because there is no sharing, so naturally there is no concurrency problem. If you need to use a thread-unsafe tool class in a concurrent scenario, the simplest solution is to avoid sharing. There are two schemes to avoid sharing , one scheme is to use this tool class as a local variable, and the other scheme is the thread local storage mode. In these two schemes, the disadvantage of the local variable scheme is that objects are frequently created in high concurrency scenarios. In the thread-local storage scheme, each thread only needs to create an instance of the tool class, so there is no problem of frequently creating objects.

The thread local storage mode is a common solution to solve concurrency problems, so the Java SDK also provides a corresponding implementation: ThreadLocal. Through our analysis above, you should be able to realize that the implementation of the Java SDK has been well thought out, but even so, it still cannot be perfect. For example, using ThreadLocal in the thread pool may still cause memory leaks, so using ThreadLocal still requires you to be energized. , Cautious enough.

In actual work, there are many platform-based technical solutions that use ThreadLocal to transfer some context information. For example, Spring uses ThreadLocal to transfer transaction information.

Demo

自己实现
/**
 * 始终已当前线程作为Key值
 *
 * @param <T>
 */
public class ThreadLocalSimulator<T> {//模拟器

    private final Map<Thread, T> storage = new HashMap<>();//非线程安全的集合类

    public void set(T t) {
        synchronized (this) {
            Thread key = Thread.currentThread();
            storage.put(key, t);
        }
    }


    public T get() {
        synchronized (this) {
            Thread key = Thread.currentThread();
            T value = storage.get(key);
            if (value == null) {
                return initialValue();
            }
            return value;
        }
    }

    public T initialValue() {
        return null;
    }
}
public class ContextTest {
    public static void main(String[] args) {
        IntStream.range(1, 5)
                .forEach(i ->
                        new Thread(new ExecutionTask()).start()
                );
    }
}

public class ExecutionTask implements Runnable {

    private QueryFromDBAction queryAction = new QueryFromDBAction();
    private QueryFromHttpAction httpAction = new QueryFromHttpAction();

    @Override
    public void run() {

        queryAction.execute();//这一步的查询结果set到ThreadLocal中
        System.out.println("The name query successful");
        httpAction.execute();//取出set的结果做自己的业务
        System.out.println("The card id query successful");

        Context context = ActionContext.getActionContext().getContext();
        System.out.println("The Name is " + context.getName() +
         " and CardId " + context.getCardId());
    }
}

public class QueryFromDBAction {

    public void execute() {
        try {
            Thread.sleep(1000L);
            String name = "Alex " + Thread.currentThread().getName();
            ActionContext.getActionContext().getContext().setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class QueryFromHttpAction {

    public void execute() {
        Context context = ActionContext.getActionContext().getContext();
        String name = context.getName();
        String cardId = getCardId(name);
        context.setCardId(cardId);
    }
    private String getCardId(String name) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "435467523543" + Thread.currentThread().getId();
    }
}

public final class ActionContext { //写成final好些,工具类来说的话

    private static final ThreadLocal<Context> threadLocal = 
                  new ThreadLocal<Context>() {
        @Override
        protected Context initialValue() {
            return new Context();
        }
    };

    private static class ContextHolder {
        private final static ActionContext actionContext = new ActionContext();
}

    public static ActionContext getActionContext() {
        return ContextHolder.actionContext;
    }

    public Context getContext() {
        return threadLocal.get();
    }

    private ActionContext(){

    }
}
Published 138 original articles · won praise 3 · Views 7208

Guess you like

Origin blog.csdn.net/weixin_43719015/article/details/105692069
Recommended