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(); } }