1、简单了解ThreadLocal
ThreadLocal是JDK包提供的一个类,它提供了线程本地变量,也就是说如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有一个这个变量的副本。当多个线程操作这个变量的时候,其实是操作自己的副本变量,也就是每个线程所拥有的本地变量,这样就避免了线程安全访问的问题。当创建了一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。
如果你还不是太理解,我这里来打个比方。线程就相当于一个人一样,而线程要做的事相当于一个任务,任务来了,人来处理,处理完毕之后,再处理下一个任务。人身上是不是有很多口袋,人刚开始准备处理任务的时候,我们把任务的编号放在处理者的口袋中,然后处理者一路携带着,处理过程中如果需要用到这个编号,直接从口袋中获取就可以了。那么刚好java中线程设计的时候也考虑到了这些问题,Thread对象中就有很多口袋,用来放东西。Thread类中有这么一个变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
这个就是用来操作Thread中所有口袋的东西,ThreadLocalMap源码中有一个数组(有兴趣的可以去看一下源码),对应处理者身上很多口袋一样,数组中的每个元素对应一个口袋。
如何来操作Thread中的这些口袋呢,java为我们提供了一个类ThreadLocal,ThreadLocal对象用来操作Thread中的某一个口袋,可以向这个口袋中放东西、获取里面的东西、清除里面的东西,这个口袋一次性只能放一个东西,重复放东西会将里面已经存在的东西覆盖掉。
2、使用ThreadLocal的简单示例
话不多说,直接上代码,看代码才能知道怎么用。
public class ThreadLocalTest {
//1.创建ThreadLocal变量 为Sring类型
public static ThreadLocal<String> localVariable = new ThreadLocal<>();
//2.创建一个打印ThreadLocal的方法
public static void printThreadLcoal(String str){
//打印当前线程的ThreadLocal变量的值
System.out.println(str+":"+localVariable.get());
//清除ThreadLocal的值
localVariable.remove();
}
public static void main(String[] args) {
//1.创建线程One
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
//设置One线程的ThreadLocal值
localVariable.set("ThreadOne local variable");
//将One线程的ThreadLocal值打印出来
printThreadLcoal("threadOne");
//打印清除之后的ThreadLocal的值
System.out.println("ThreadOne remove after"+":"+localVariable.get());
}
});
//1.创建线程Two
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
//设置Two线程的ThreadLocal值
localVariable.set("ThreadTwo local variable");
//将Two线程的ThreadLocal值打印出来
printThreadLcoal("threadTwo");
//打印清除之后的ThreadLocal的值
System.out.println("ThreadTwo remove after"+":"+localVariable.get());
}
});
//启动线程
threadOne.start();
threadTwo.start();
}
}
代码里面都有注释,相信大家都能看懂。其中无非就是set和get还有remove几个方法。
3、从源码深入理解ThreadLocal
从上面我们可以知道,ThreadLocal是用来操作口袋的,也就是往口袋里面装东西,拿东西的。而口袋又是属于线程的。所以让我们先来看看Thread的源码中有关口袋的定义:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
翻译一下注释里面的内容:第一段是与此线程相关的threadlocal值。此映射由threadlocal类维护。第二段是与此线程相关的可继承的threadlocal值。此映射由threadlocal类维护。
单看翻译好像不太能理解哦。我这里解释一下,其实这就是在Thread类里面定义了两个变量,是Map类型的,也就是键值对形式存在的,也就是我们所说的口袋。每个线程都会有的。我们ThreadLocal操作的正是这两个成员变量。只有当线程第一次调用ThreadLocal的set或者get方法时才会创建他们,也就是实例化。
接下来我们来看看ThreadLocal是怎样往口袋里面装东西的。下面是源码:代码的解释也在下面
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的Map,也就是拿到线程的口袋
ThreadLocalMap map = getMap(t);
//判断口袋里面是否为空
if (map != null)
//不是空的,就把当前ThreadLocal对象作为key,value值自己给定
map.set(this, value);
else
//是空的就创建一个Map
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
下面看看他怎么获得口袋里面的东西吧
public T get() {
//获得当前线程
Thread t = Thread.currentThread();
//获得当前线程的口袋
ThreadLocalMap map = getMap(t);
//判断Map是否为空
if (map != null) {
//通过当前ThreadLocal对象找到对应的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//设置初始值,并返回初始值,这里的初始值为null
private T setInitialValue() {
//将null赋值给value,initialValue()返回的就是null
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//获得口袋
ThreadLocalMap map = getMap(t);
//日常判断一下口袋是否为空
if (map != null)
map.set(this, value);
else
createMap(t, value);
//返回null
return value;
}
4、ThreadLocal不支持继承性
先来看一段代码吧
public class ThreadLocalExtendTest {
//1.创建ThreadLocal变量 为Sring类型
public static ThreadLocal<String> localVariable = new ThreadLocal<>();
public static void main(String[] args) {
//在父线程的口袋里面加入HelloWorld
localVariable.set("Hello World");
//创建并启动子线程
new Thread(new Runnable() {
@Override
public void run() {
//输出子线程口袋里面的值
System.out.println("child:"+localVariable.get());
}
}).start();
//输出父线程口袋里面的值
System.out.println("main:"+localVariable.get());
}
}
输出结果:
main:Hello World
child:null
从上面的代码来看,ThreadLocal在父线程中被设置后,在子线程里面是获取不到,因为这是两个不同的线程,只有父线程口袋里装了东西,子线程却并没有继承父线程的口袋,所以ThreadLocal并没有继承性。