ThreadLocal
前言
ThreadLocal:线程局部变量
在并发操作的时候,如何保证数据的安全性? 加锁!加锁固然解决问题,同时也是要牺牲一定的性能。
ThreadLocal就冒出来了,它的作用就是数据隔离。在并发操作下,避免当前线程的变量被其他线程修改,保证了数据的安全性
如果说锁和ThreadLocal之间有什么区别的话,就引用下敖丙前辈在相关博文中的一句话:“锁的应用是解决可能出现的问题,ThreadLocal是避免这种问题的发生”
这篇文章主要还是帮助KK来记忆相关知识的,如果有什么表达或者理解不对的地方,欢迎大家指正和交流
ThreadLocal的使用
ThreadLocal的使用非常简单,如下:
//初始化一个ThreadLocal实例
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
//为线程局部变量赋值
threadLocal.set(1);
//获取线程局部变量的值
threadLocal.get();
//清除值
threadLocal.remove();
源码
SET
public class ThreadLocal<T> {
//...
public void set(T value) {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程的ThreadLcoalMap,每个线程都有自己的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//存在,赋值
map.set(this, value);
else
//不存在,创建一个ThreadLcoalMap
createMap(t, value);
}
//...
ThreadLocalMap getMap(Thread t) {
//因为ThreakLocalMap是当前线程的,所以需要当前线程实体
//threadLocals定义在Thread中
return t.threadLocals;
}
//...
void createMap(Thread t, T firstValue) {
//ThreadLocalMap的是KEY是ThreadLocal实体
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
GET
public class ThreadLocal<T> {
//...
public T get() {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
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;
}
}
//当前ThreadLocalMap不存在,进行初始化
//将当前ThreadLcoal对象作为KEY,null为VALUE,构建了ThreadLocalMap
return setInitialValue();
}
}
REMOVE
public class ThreadLocal<T> {
//...
public void remove() {
//获取当前线程的ThreadLocalMap,进行map的remove操作
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
}
为什么ThreadLocal可以保证并发操作下数据的安全性?
正如源码所见,每个线程都定义了属于自己的ThreadLocalMap(在Thread中声明了的threadLocals变量),在Set和Get操作的时候,都是先获取当前线程实例,再通过实例获取线程的ThreadLocalMap,然后对Map进行一些列操作,例如赋值,取值,初始化等等。
当前线程无法获取到其他线程的ThreadLocalMap,自然也无法对其他线程的数据进行操作了,避免了冲突,保证了数据的安全性。
public class Thread implements Runnable {
//...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
withInitial()
单独把这个方法拎出来,是因为这玩意真的花了不少时间,需要记录下,主要是也怕自己忘记了
如果说上面是单一线程的操作,如果需要对所有线程进行相同的初始化赋值就可以使用ThreadLcoal.withInitial()
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
public class ThreadLocal<T> {
//创建一个线程局部变量。变量的初始值是通过调用 Supplier 的 get 方法来确定的
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
//判空操作,可能会抛NPE
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
//统一初始化赋值的时候,程序会调用当前的初始化方法。supplier.get()获取的就是你设置的那个值
@Override
protected T initialValue() {
return supplier.get();
}
}
}
public class ThreadLocal<T> {
//...
//ThreadLcoal.get()中如果没有获取到TreadLocalMap,会调用当前方法,赋予默认值NULL
private T setInitialValue() {
//如果初始化调用了withInitial(),当前方法会被重写。如果未被重写,默认是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;
}
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
* <p>This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
//上面这段注释比较重要的意思是后面,说的是如果需要NULL以外的初始值,需要重写当前方法,并且使用匿名内部类
protected T initialValue() {
return null;
}
}
首先说下结论:上述初始化赋值时,只是进行构建ThrealLocal操作,赋值的过程是在调用get方法时进行的
在我DEBUG调试的过程中,发现它是先构建ThreadLocal实体。只有当其他线程执行get方法之时会执行setInitialValue()方法进行初始化值。程序默认的初始值是NULL,但是SuppliedThreadLocal这个类重写了这个方法,它会通过supplier.get()方法去获取设置的值,然后进行初始化赋值操作。
然后再浏览相关博文的时候发现,看到一种说法,经过withInitial()方法初始化的ThreadLocal,通过Set和Remove后,你再次Get会发现还是初始值,并不是NULL。我自己试了试还真是这样。
因为Remove后,当前线程的ThreadLocalMap是没有值的,Get操作会执行设置初始值的操作setInitialValue(),自然就会调用被重写的initialValue()方法,初始值当然不会是NULL了,有种拨开云雾见青天的感觉。芜湖!
结尾
ThreadLocal还有关于强弱引用和内存溢出的问题,这点KK还没开始研究。给自己挖个坑先。
散会!