ThreadLocal
目录:
-
为什么要使用ThradLocal -
如何使用ThreadLocal -
ThreadLocal如何做到线程隔离 -
ThreadLocal会出现什么问题
一.为什么要使用ThradLocal
提到ThradLocal我们就不得不提到多线程并发情况的安全问题,很多情况下我们会使用synchronized或者Lock去实现给对象上锁的方式去解决线程安全,但是这种重量级锁的方式降低了效率,有一些业务场景下是完全没必要牺牲那么大的效率的,所以ThreadLocal出现了,它并不是可以完全替代锁,他只是解决线程安全问题的另一种思路而已.ThreadLocal很容易翻译(本地变量)其实就是通过线程隔离的方式去解决并发导致的问题.
二.如何使用ThreadLocal
public class ThreadLocalTest {
public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
public static void main(String[] args) {
IntStream.range(0, 5).forEach(i -> new Thread(() -> {
THREAD_LOCAL.set(Thread.currentThread().getName() + ":" + i);
System.out.println("线程名称:" + Thread.currentThread().getName() + "值:" + THREAD_LOCAL.get());
}).start());
THREAD_LOCAL.remove();
}
}
我们可以简单看到每一个线程名称里都维护了自己的一个值,多线程在执行的过程中,我们并不能确定到底是那个线程先往THREAD_LOCAL里添加了线程的值(set),但是打印结果出现我们却发现每一个线程都是拿到了自己线程所set的值,我们带着这个问题:
-
多线程无论那个线程先执行往ThradlLoca中Set值,最后拿到的一定是自己线程Set的值?
三.ThreadLocal 为什么可以实现线程隔离
开始源码了!!!!!!!!! 非战斗人员请撤离
-
介绍下ThreadLocal的源码基础 感叹一下Josh Bloch,Doug Lea 两个佬
接下来我们看下Set的源码
public void set(T value) {
#这一行翻译很简单 Thread.currentThread 拿到当前线程
Thread t = Thread.currentThread();
# 然后发现有一个getMap(t)的方法把当前线程当作参数传进去了来看下getMap()
ThreadLocalMap map = getMap(t)的方法把当前线程当作参数传进去了来看下getM(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
-
set的源码到现在的意思是拿到当前线程去调用getMap这个方法ok继续往下走
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
-
我们发现getMap只是去调用当前线程的ThreadLocalMap 这就让我们引发了猜想 很想去看看当前线程的ThreadLocalMap是个什么玩意呢
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
-
卧槽 我们打开了Thread线程的源码之后发现这个ThreadLocalMap就是当前线程的成员变量我们回头去看set的源码。
-
!!!大伙看起来怎么样,有没有一种猜想,拿到当前线程获取当前线程的成员变量作为容器去存储数据,每一个线程都维护了这样的一个ThradLocalMap,自然而然多个线程在获取值的时候获取的就是自己当前线程维护这个Map的value,而且线程只能获取自己当前维护的Map,所以才实现了线程隔离(有点绕口,慢慢消化)
set的源码告一段落先消化,然后我们来看一下get和remove的源码(如果你理解了set其实get和remove很简单来看一下)
public T get() {
# 获取当前线程
Thread t = Thread.currentThread();
# 拿到当前线程维护的map
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();
}
public void remove() {
# 拿到当前线程维护的Map
ThreadLocalMap m = getMap(Thread.currentThread());
# 删了它
if (m != null)
m.remove(this);
}
-
我觉得我完全没有必要做过多的赘述
关于他是如何实现线程隔离的基本概念已经大概了解了,线程维护单独的Map去实现的那我们也简单看下ThreadLocalMap的源码去分析下他的set
-
第一眼看源码的时候我估计大部分人都会发现这个问题,ThreadLocalMap并没有实现Map接口而是继承了WeakReference,好奇心驱使我点进去看了下WeakReference,他叫弱引用,他是干嘛的?
WeakReference如字面意思,弱引用, 当一个对象仅仅被weak reference(弱引用)指向, 而没有任何其他strong reference(强引用)指向的时候, 如果这时GC运行, 那么这个对象就会被回收,不论当前的内存空间是否足够,这个对象都会被回收。通俗一点理解就是这个对象没有被引用的话就会在下一次GC的时候回收掉(理解起来很模糊,需要你对GC和堆栈有一定的了解)
-
ThreadLocalMap的set源码!
private void set(ThreadLocal<?> key, Object value) {
#拿到当前数组
Entry[] tab = table;
int len = tab.length;
#根据ThreadLocal对象的hash值定位到table中的位置i
int i = key.threadLocalHashCode & (len-1);
#遍历table
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
#当前ThreadLocal的key值
ThreadLocal<?> k = e.get();
#如果这个Entry对象的key正好是即将设置的key那么就直接覆盖之前的Value!!!不存在链表
if (k == key) {
e.value = value;
return;
}
#判断Entry对象的key是否是null 是null的话就会初始化一个Entry对象放在位置I上面
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
# 如果位置i的不为空,而且key不等于entry,那就找下一个空位置,直到为空为止。
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
看这张图理解下set的过程
四.ThreadLocal会出现什么问题
还记得这个弱引用嘛
ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了,而这key又是ThreadLocal自己
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。简单了解下就好
解决: 使用完一定remove掉entity就完事了
ThreadLocal<String> localName = new ThreadLocal();
try {
localName.set("张三");
……
} finally {
localName.remove();
}
ThreadLocal 如何实现的线程隔离以及会出现的问题我们都已经看完了,是不是觉得其实源码也没有那么难读懂,读源码一定要慢慢来去品一下他的设计思想,不要着急,希望这篇文章能让你对ThreadLocal有一个更深入的了解.
原文始发于微信公众号(闯sir9):ThreadLocal 线程隔离? 15分钟一篇文章就懂啦?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/20626.html