ThreadLocal详解

ThreadLocal详解

参考:

www.threadlocal.cn/#threadlocal1 - 开门见山ThreadLocal

https://zhuanlan.zhihu.com/p/102571059 -ThreadLocal的内存泄露?什么原因?如何避免?

利用ThreadLocal可以实现隐式的参数传递,另外由于每个线程都有各自的Map,从而可以各自持有各自的数据,不会存在多线程并发安全问题,所以也可以作为多线程并发安全问题解决方案之一

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

 

ThreadLocal接口方法

set

void set(Object value)

设置当前线程的线程局部变量的值

get

public Object get()

该方法返回当前线程所对应的线程局部变量

remove

public void remove()

将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法

initialValue

protected Object initialValue()

返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的

 

实现原理 - 简易版本

在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本

Map中元素的键为线程对象,而值对应线程的变量副本

 public class MyThreadLocal
 {
 ​
     private final ConcurrentHashMap<Thread, Object> valueMap = new ConcurrentHashMap<>();
 ​
     public void set(Object newValue)
     {
         valueMap.put(Thread.currentThread(), newValue);
 ​
     }
 ​
     public Object get()
     {
         Thread currentThread = Thread.currentThread();
 ​
         Object o = valueMap.get(currentThread);
 ​
         if (o == null && !valueMap.containsKey(currentThread))
         {
 ​
             o = initialValue();
 ​
             valueMap.put(currentThread, o);
 ​
         }
 ​
         return o;
 ​
     }
 ​
     public void remove()
     {
         valueMap.remove(Thread.currentThread());
 ​
     }
 ​
     public Object initialValue()
     {
         return null;
     }
 }
 

  

ThreadLocal的内存泄漏

什么是内存泄漏呢?简单的说,就是东西放在内存里面,但你忘记它放哪里了,它占着一块内存,但是不能回收。当这样的东西越来越多,内存就吃紧,最终导致服务器宕机

再讲一个小故事,阐述一下内存泄漏

在抗日时期,有两名地下党A和B,A是上线,B是下线,B不能直接联系党中央的,他需要通过A来帮忙传话。一旦A发生意外,党中央就找不到B了,B一直存在,但是茫茫人海,党中央是无法启用B做战斗任务的安排,这种情况类似内存泄漏

ThreadLocal实现原理

ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap

 static class ThreadLocalMap {
 ​
     static class Entry extends WeakReference<ThreadLocal<?>> {
         /** The value associated with this ThreadLocal. */
         Object value;
 ​
         Entry(ThreadLocal<?> k, Object v) {
             super(k);
             value = v;
         }
     }
     ...
    }

  

ThreadLocal 内存泄漏的原因

hreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉

 

一个有趣的的应用场景: 将类改造成上下文类

 static class ErrorContext
     {
         private List<String> messages = new ArrayList<String>();
 ​
         private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
 ​
         private ErrorContext()
         {
 ​
         }
 ​
         public static ErrorContext getInstance()
         {
             ErrorContext context = LOCAL.get();
             if (context == null)
             {
 ​
                 context = new ErrorContext();
                 LOCAL.set(context);
             }
             return context;
         }
 ​
         public ErrorContext message(String message)
         {
             this.messages.add(message);
             return this;
         }
 ​
         public ErrorContext reset()
         {
 ​
             messages.clear();
             LOCAL.remove();
             return this;
         }
 ​
         @Override
         public String toString()
         {
             StringBuilder description = new StringBuilder();
 ​
             for (String msg : messages)
             {
                 description.append("### ");
                 description.append(msg);
                 description.append("\n");
             }
 ​
             return description.toString();
         }
 ​
         public static void main(String[] args)
         {
 ​
             ErrorContext cxtMain = ErrorContext.getInstance();
 ​
             cxtMain.message("Main Thread Message");
             System.out.println(cxtMain);
             cxtMain.reset();
 ​
             Runnable task1 = () -> {
 ​
                 ErrorContext cxtTask1 = ErrorContext.getInstance();
                 cxtTask1.message("Task1 Thread Message");
                 System.out.println(cxtTask1);
                 cxtTask1.reset();
             };
 ​
             Runnable task2 = () -> {
                 ErrorContext cxtTask2 = ErrorContext.getInstance();
                 cxtTask2.message("Task2 Thread Message");
                 System.out.println(cxtTask2);
                 cxtTask2.reset();
             };
 ​
             new Thread(task1).start();
 ​
             new Thread(task2).start();
 ​
         }
     }

  

 

posted @ 2021-09-02 10:31  minnersun  阅读(105)  评论(0编辑  收藏  举报