ThreadLocal那点事
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal那点事相关的知识,希望对你有一定的参考价值。
今天看自己项目中的代码遇到一个ThreadLocal,平时没遇到过,于是我决定自己好好去查查资料,这个是干嘛用的
1、ThreadLocal是什么
从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的点,使用场景那也是相当的丰富:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。
现在相信你已经对ThreadLocal有一个大致的认识了,下面我们看看如何用?
2、ThreadLocal怎么用
既然ThreadLocal的作用是每一个线程创建一个副本,我们使用一个例子来验证一下:
public static void main(String[] args)
ThreadLocal<String> local = new ThreadLocal<>();
Random random = new Random();
IntStream.range(0,5).forEach((a)->
new Thread(()->
String str = random.nextInt(10)+"";
local.set(str);
System.out.println("线程"+Thread.currentThread().getName()+"的值: "+ local.get());
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
).start();
);
从结果我们可以看到,每一个线程都有各自的local值,我们设置了一个休眠时间,就是为了另外一个线程也能够及时的读取当前的local值。
如果说这个感觉还是不够再看下面的
private static ThreadLocal<String> local = new ThreadLocal<>();
public static void main(String[] args)
int i1 = Runtime.getRuntime().availableProcessors();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(i1);
for (int i = 0; i < 8; i++)
int finalI = i;
fixedThreadPool.execute(new Runnable()
public void run()
try
String s = local.get();
if(s==null || "".equals(s))
local.set(finalI+"");
String s2 = local.get();
System.out.println("线程"+Thread.currentThread().getName()+ "值1: " + s +"值2: " + s2);
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
);
从结果上看,前四个记录说明了,数据在线程之间是隔离的,后四条和前四条对比着看说明数据是每个线程只能读取自己的
3、ThreadLocal源码分析
设置值
public void set(T value)
//获取当前线程
Thread t = Thread.currentThread();
//这个就是获取当前线程的map,
//ThreadLocalMap 目前就理解为普通map即可,key是ThreadLocal,value就是你设置的值
ThreadLocalMap map = getMap(t);
if (map != null)
//如果存在就进行修改里面的值
map.set(this, value);
else
//如果不存在就创建map,并把当前值放入
createMap(t, value);
void createMap(Thread t, T firstValue)
t.threadLocals = new ThreadLocalMap(this, firstValue);
获取值
public T get()
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//当前线程已经存在map的情况下自己取相应的值即可
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
//如果当前线程不存在返回一个初始化的值
return setInitialValue();
private T setInitialValue()
//这个地方返回的是null,这也就明白线程第一次获取为啥是null了
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
protected T initialValue()
return null;
从get和set方法对比可以,不管你调用的是get还是set一旦调用该线程就存在自己的map了,只是get存放的null,set存放的是你自己设置的值而已
删除:
public void remove()
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
其实内部源码很简单,现在我们总结一波
(1)每个Thread维护着一个ThreadLocalMap的引用
(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
(3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。
(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中
(5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。
(6)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
4、ThreadLocal内存泄漏问题
为何存在内存泄漏的问题,这个地方要从ThreadLocalMap说起,刚才没有看这个类是什么我们先看看:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
ThreadLocalMap 就是ThreadLocal一个内部类,内部初始化的Entry这个类,但是Entry是弱引用
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1、Thread中有一个map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
以上是关于ThreadLocal那点事的主要内容,如果未能解决你的问题,请参考以下文章