注意:本篇有因代码均为截图,如果喜欢看代码块请移步https://blog.csdn.net/qq_23633427/article/details/82054252
一、什么是ThreadLocal
ThreadLocal被大多数人叫线程本地变量,ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。
二、使用ThreadLocal与ThreadLocal是如何实现的
下面看一段代码例子,图2-1。
图2-1
如果stringThreadLocal就是正常的String变量的话在第一次输出的时候应该输出
线程id:1,线程名:main
第二三次输出的时候应该输出的都是新创建线程的线程id与线程名
那么我们来看一下实际结果
线程id:1,线程名:main
线程id:12,线程名:Thread-0
线程id:1,线程名:main
执行后我们发现第三次输出的还是主线程的id与线程名,也就是说ThreadLocal起到了作用,他在新的线程中创建了一个变量副本,而没有去修改主线程中的变量。
那么ThreadLocal是如何做到的呢,首先我们先看一下ThreadLocal提供的方法。
//get()是用来获取ThreadLocal在当前线程中保存的副本
public T get() { }
//set() 是用来设置当前线程中的变量副本
public void set(T value) { }
//remove() 用来清除当前线程中的变量副本
public void remove() { }
// initialValue() 这个方法是需要使用的时候实现,具体的作用会在讲解get方法时说到
protected T initialValue() { }
首先我们先来看看get()方法的实现,图2-2。
图2-2
这段代码首先获取了当前线程,随后调用了一个叫做getMap()的方法,获取类一个类型为ThreadLocalMap的Map,随后通过传入this,获取到键值对,如果成功获取到键值对则返回value,如果没有获取到map或键值对e都会去调用setInitialValue()方法。
在概括的说明了get方法之后,我们从上到下看一下get方法中所调用的方法都干了些什么。
首先是getMap(),我们去看一下ThreadLocalMap是从哪里获得的,图2-3。图2-3
令人震惊的是他直接返回了当前线程的一个成员变量threadLocals,那我们再去看一下Thread类中的成员变量,图2-4。
图2-4
在查看了成员变量threadLocals后我们发现Thread中的成员变量threadLocals就是一个ThreadLocalMap,而这个类型是一个ThreadLocal的内部类。
ThreadLocalMap的Entry继承了WeakReference,并使用了ThreadLocal作为键值。
那么看完了getMap(),我们继续来看setInitialValue(),图2-5
图2-5
这里就用到了之前说过的需要自己进行实现的方法initialValue(),咱们先不管他只需要知道他给我们返回了一个泛型T就可以了,之后的步骤就很容易了解了,与之前说的一样先去获得当前线程然后getMap(),判断map是否为null,如果为null创建并存入一个一个键值对,如果不为null,直接向map中存入一套键值对。
看完整个setInitialValue()方法,大家会发现initialValue()返回的value实际上就是存入键值对时的value,那我们去看一下没有重写的情况下initialValue()返回的是什么,图2-6。图2-6
没错!是null哒!(皮一下.jpg)
按照我们刚才的理解如果不事先进行set(),直接get()的话应该会返回给我们一个null,如果我们自己重写了initialValue()方法那么就会按照我们重写的方法返回给我们对应的对象,那现在我们来试一试。
首先我们去掉所有的set()执行一遍试试,按照预期的想法他应该会输出null,图2-7。图2-7
下面看一下执行结果:
null
null
null
嗯,和预期的结果一样都是null,那下面我们来重写initialValue()方法试一下,图2-8。图2-8
看一下执行结果:
线程id:1,线程名:main
线程id:12,线程名:Thread-0
线程id:1,线程名:main
和分析的结果一样,重写了initialValue()方法后,不需要我们去set()直接调用get()方法也能获取到我们想要的内容。
三、withInitial(Supplier extends S> supplier)
如果你不愿意去重写initialValue()方法,java8也为你提供了新的方案,通过函数式编程的方式来生成一个ThreadLocal,多说无益,直接修改一下我们的例子,图3-1。
图3-1
修改后执行结果:
线程id:1,线程名:main
线程id:12,线程名:Thread-0
线程id:1,线程名:main
与之前的代码一致。
四、总结
通过ThreadLocal创建的副本是保存在每个Thread中自己的成员变量threadLocals中的。
ThreadLocalMap的键使用ThreadLocal对象是因为每个线程中可能有许多ThreadLocal变量。
如果不希望调用set()方法请重写initialValue()方法,或通过withInitial()创建ThreadLocal对象,否则直接调用get()方法返回为null。
如果想在get()之前不需要调用set()的话,必须重写initialValue()方法,或通过withInitial()创建ThreadLocal对象。
ps:不能直接贴代码段好烦.......,我为什么忽然间脑子一抽想在B站也发一版。