ThreadLocal 前言
ThreadLocal:线程局部变量
在并发操作的时候,如何保证数据的安全性? 加锁!加锁固然解决问题,同时也是要牺牲一定的性能。
ThreadLocal就冒出来了,它的作用就是数据隔离。在并发操作下,避免当前线程的变量被其他线程修改,保证了数据的安全性
如果说锁和ThreadLocal之间有什么区别的话,就引用下敖丙前辈在相关博文中的一句话:“锁的应用是解决可能出现的问题,ThreadLocal是避免这种问题的发生”
这篇文章主要还是帮助KK来记忆相关知识的,如果有什么表达或者理解不对的地方,欢迎大家指正和交流
ThreadLocal的使用
ThreadLocal的使用非常简单,如下:
//初始化一个ThreadLocal实例ThreadLocal threadLocal = new ThreadLocal<>();//为线程局部变量赋值threadLocal.set(1);//获取线程局部变量的值threadLocal.get();//清除值threadLocal.remove();
源码 SET
public class ThreadLocal { //... 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 { //... 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 { //...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.ThreadLocalMap threadLocals = null; }
withInitial()
单独把这个方法拎出来,是因为这玩意真的花了不少时间,需要记录下,主要是也怕自己忘记了
如果说上面是单一线程的操作,如果需要对所有线程进行相同的初始化赋值就可以使用ThreadLcoal.withInitial()
ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 1);public class ThreadLocal { //创建一个线程局部变量。变量的初始值是通过调用 Supplier 的 get 方法来确定的 public static ThreadLocal withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } static final class SuppliedThreadLocal extends ThreadLocal { 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 { //... //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; } //上面这段注释比较重要的意思是后面,说的是如果需要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还没开始研究。给自己挖个坑先。
散会!