public class ThreadLocalTest {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
ThreadLocalTest tlt = new ThreadLocalTest();
new Thread(()->{
// 设置值
tlt.threadLocal.set("hello");
// 获取值
System.out.println(tlt.threadLocal.get());
// 如果不用了,要及时清除掉
tlt.threadLocal.remove();
}).start();
}
}
这里提一下为什么要记得使用remove方法:
-
如果当前线程一直不消亡, 那么这些本地变量会一直存在, 所以可能会造成内存溢出, 因此使用完毕后要记得调用ThreadLocal 的remove 方法删除对应的本地变量;
ThreadLocal的使用看起来如此简单,下面带大家了解一下ThreadLocal的原理。
其实Thread类里有两个变量,如下。
// 与ThreadLocal变量有关
ThreadLocal.ThreadLocalMap threadLocals = null;
// 与InheritableThreadLocal变量有关
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
InheritableThreadLocal是ThreadLocal的子类,我们后面再讲,先重点关注ThreadLocal即可。
为什么要列举出Thread类里面的这两个变量呢?当然是因为它们与ThreadLocal的原理有关!
ThreadLocal的使用无非就是get()、set()、remove()方法,下面我们先看一下ThreadLocal::set()方法的源码。
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
if (map != null)
// 设置值
map.set(this, value);
else
// 创建threadLocals,并设置值
createMap(t, value);
}
// 获取指定线程的threadLocals变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
通过上面的源码分析,我们有如下结论:
-
ThreadLocal其实只是一个空壳,而所谓的线程本地变量其实是存储在Thread类的threadLocals变量里的;
-
threadLocals是ThreadLocal.ThreadLocalMap类型,ThreadLocal::set调用ThreadLocalMap::set将线程本地变量放置到threadLocals中;
-
Thread的threadLocals变量初始值是null,当第一次设置值的时候才会实例化;
下面我们再来深入看一下ThreadLocalMap::set()方法的源码。
public class ThreadLocal<T> {
static class ThreadLocalMap{
// ThreadLocal是一个弱引用,一旦发生垃圾回收就会清除
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 哈希表,为什么要设计成哈希表呢?因为每个线程可以有多个ThreadLocal变量
private Entry[] table;
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 哈希函数,做&运算,获取下标
int i = key.threadLocalHashCode & (len-1);
// 注意看这个for循环,用的是线性探测法
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 表示这个ThreadLocal是第二次设置值,则新值会覆盖旧值
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
// 内部会调用resize()方法
rehash();
}
}
}
通过上述源码和注释,我们了解到ThreadLocalMap维护了一个使用线性探测法的哈希表Entry[]来存储多个线程本地变量,下标计算方式=ThreadLocal::threadLocalHashCode() & (length-1),这里的hashcode其实是通过AtomicInteger获取的。因此,ThreadLocalMap是一个定制化的Map。
而至于线程如何将threadLocals变量存储在线程本地而不共享,就不是Java层面的事,与本地库c或者c++的实现有关了。
而ThreadLocal有一个问题,就是子线程无法获取父线程的ThreadLocal变量,即不可继承。因为子线程与父线程是两个线程,因此无法获取是很正常的。
而前面提到的InheritableThreadLocal就是来解决这个问题的,InheritableThreadLocal是可继承的。
ThreadLocal不可继承性与InheritableThreadLocal可继承性示例代码如下。
public class ThreadLocalTest {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
ThreadLocalTest tlt = new ThreadLocalTest();
tlt.threadLocal.set("ok");
tlt.inheritableThreadLocal.set("123");
new Thread(()->{
//获取父线程的值
// 输出null
System.out.println(tlt.threadLocal.get());
// 输出123
System.out.println(tlt.inheritableThreadLocal.get());
}).start();
}
}
原文始发于微信公众号(初心JAVA):并发第五弹 ThreadLocal原理
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之家整理,本文链接:https://www.bmabk.com/index.php/post/35544.html