前言
在并发情况下为了保证线程安全往往会选择加锁,但是无论是哪种锁总对性能有所影响,而使用ThreadLocal可以为线程创建一个独享变量,从而避免线程间竞争的情况,达到线程安全的作用。
ThreadLocal也是面试过程当中经常会问到的,所以对于准备面试的同学也是很有必要学习ThreadLocal的。
先给出几个面试题,本文后后面会给出答案:
ThreadLocal的基本使用
创建
ThreadLocal threadLocal = new ThreadLocal();
ThreadLocal.withInitial静态方法创建一个带有初始值的ThreadLocal
参数是一个Supplier的函数式接口
如果对函数式接口不了解的可以看我的之前的分享 函数式接口一文看懂
ThreadLocal withInitialValueThreadLocal = ThreadLocal.withInitial(()->"hello,ThreadLocal");
两种方式差不多,只是第二种方式会自带一个初始值
赋值
赋值只需要调用ThreadLocal的set方法,就可以将值保存到ThreadLocal中
ThreadLocal变量也是一个变量,使用上完全可以把他当作一个普通的变量来使用,只是他天生是线程安全的,因为这个变量的值不会受其他任何线程所影响
threadLocal.set("aaa");
取值
赋值只需要调用ThreadLocal的get()方法,不需要任何参数
threadLocal.get(); // aaa
删除
ThreadLocal和普通变量不同的地方在于不用时建议手动删除,避免内存泄露(虽然不手动删除也不一定内存泄露,但是还是建议手动删除)
threadLocal.remove()
ThreadLocal变量为什么线程独享的呢?
原理图
这是我从一文详解ThreadLocal截取过来的原理图,先大致讲一下。
每个线程Thread内部都有一个ThreadLocalMap,为ThreadLocal赋值其实就是到线程内部的Map里插入一个以ThreadLocal变量作为key,变量值为Value的键值对,取值也是去线程内部的ThreadLocalMap中以当前的ThreadLocal变量作为key调用Map的get方法返回结果,remove则是在ThreadLocalMap中删除以ThreadLocal变量作为了Map的key的键值对。
现在看不懂也没关系,下面就来从源码来看一下ThreadLocal的工作流程
ThreadLocal-set方法
流程解析
源码分析
public void set(T value) {
Thread t = Thread.currentThread(); //取得当前的线程
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value); // this 这是ThreadLocal的方法,指代的就是当前的ThreadLocal
} else {
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 取得线程内部的ThreadLocalMap
}
//
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); // 创建Map 并加入键值对
}
get方法
流程解析
源码分析
public T get() {
Thread t = Thread.currentThread(); //取得当前的线程
ThreadLocalMap map = getMap(t); //取得当前线程内部的ThreadLocalMap
if (map != null) { //程内ThreadLocalMap还未创建
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) { // ThreadLocalMap内还未保存当前ThreadLocal的键值对
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); // 有初始化方法则初始化并返回初始值,没有返回null
}
remove方法
流程解析
源码分析
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread()); //取得当前的线程并取得当前线程内部的ThreadLocalMap
if (m != null) {
m.remove(this); //删除以当前ThreadLocal变量作为key的键值对
}
}
从上面的部分其实已经基本了解了ThreadLocal的工作原理了,但是你会发现他所有set、get、remove其实都是调用了线程内部那个ThreadLocalMap的方法,所以下面我们就更深度的解析一下ThreadLocalMap的源码,面试经常问到的内存泄露问题在了解了源码之后也很好理解了。
ThreadLocalMap源码解析
ThreadLocalMap的源码有两个原因导致其比较复杂
ThreadLocalMap内部却是使用的开放地址法中的线性探测法来处理的Hash冲突
ThreadLocalMap为了尽可能避免内存泄露,所以Entry的Key值(ThreadLocal)使用的是弱引用,也就是说随时都有可能存在Map里某个Entry的Key被GC回收了变成了null值,他在每次get、set和remove操作时都需要考虑某些特殊情况下GC可能引起的错误,以及每次都会清除一部分被GC的清理掉Key值的Entry
static class Entry extends WeakReference