zl程序教程

您现在的位置是:首页 >  Java

当前栏目

聊聊Java中的ThreadLocal作用

2023-02-25 18:07:08 时间

在java中,如果我们多线程操作变量的时候,需要加上同步控制机制,原因是多线程操作一个变量,那么如果每个线程都操作自己线程的变量,那就不用加锁了,也不用加同步控制了。

ThreadLocal就是这个作用,比如在Web开发中,我们用ThreadLocal来保存用户信息,然后传递后台多个service,然后每个线程单独获取自己的用户信息;

初始化代码也比较简单:

public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("mm:ss");
};

使用比较简单,通过重载initialValue()这个方法进行初始化,或通过set进行设置,然后get使用即可,整个使用过程类似于HashMap。

那如何神奇的控制不同的线程保存不同的数据,从而达到线程的共享那,如下:

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

首先代码中通过Thread.currentThread()来获取当前的线程id,通过线程id获取对应的ThreadLocalMap,这个getMap,其实是获取Thread的成员变量如下:

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

此成员变量定义如下:

ThreadLocal.ThreadLocalMap threadLocals = null;

然后再来看这句话:

ThreadLocalMap.Entry e = map.getEntry(this);

即通过本ThreadLocal的对象作为key,获取Entry对象后,再获取它的value,如果为null那,那就调用setInitialValue()进行初始化,代码如下:

private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

此线程的map如果存在,不为null,直接更新,返回默认的初始化值,即initialValue()的返回值,如果不存在,则调用createMap(t,value);来创建map,如下:

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

说实话代码挺绕的,找了网上一张图,会形成如下的结构:

【图来自互联网,侵权删除】

我们从这个图可以看到,ThreadLocal是所有线程的map的公共key,还要注意到,这个map比较特殊,是内部自己实现的,通过线性探测的方法来解决哈希冲突的,即如果槽位已经被占用了,则通过一个函数计算得到下一个槽位, 这种方法解决冲突的效率比较低,所以不建议用太多的ThreadLocal变量。

Threadlocal相关的数据结构:

【图片来自互联网,侵权删除】

【图片来自互联网,侵权删除】

从上图中可以看到Entry继承自弱应用,下次gc的时候会回收,但是只有key是弱引用,value还是强引用,下次gc的时候,key被回收而value可能一直不会被回收。

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

所以解决办法是,使用过之后记得通过remove()进行删除。

总结:

ThreadLocal适用于无状态的线程内变量共享的场景,比如我们说的通过ThreadLocal保存每个线程特有的信息,比如线程标识(打日志的时候适用,便于排查问题)。

ThreadLocal有一定的内存泄漏分享,记得要remove。