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

浙公网安备 33010602011771号