写在前面
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。本篇文章将带你详细的剖析一下ThreadLocal,以及在开发中的使用。
代码分析
首先ThreadLocal这个类是位于java.lang这个包中,具体源码可以自行查看。那么ThreadLocal到底是干嘛用的呢?其实它主要就用来在当前线程中存取变量用的,存取变量就存取变量怎么多了一句当前线程中?不要急接着往下看。说到存取变量我们平时可能用到的例如:List,Map,数组等,那么ThreadLocal又是通过什么样方式进行变量的存取呢?答案就是Map(键值对),没错就是Map,而这个Map不是其他的Map而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,在每一个线程Thread中都有一个ThreadLocalMap的成员变量,不信的话看一下Thread类的源码:
public class Thread implements Runnable {
.......这里省略掉之前的代码
ThreadLocal.ThreadLocalMap threadLocals = null;
.......这里省略掉之后的代码
}
这里你会发现在Thread类中确实是有一个ThreadLocalMap的成员变量threadLocals,可以很明确的告诉你ThreadLocal就是在操作这个Thread中的threadLocals,来进行变量的存取,置于为什么ThreadLocal能操作Thread中的threadLocals,以及如何操作的后面通过源码的分析更进一步的了解。截止到目前你需要知道以下几点:
- 1、 ThreadLocal是在用于存取变量的
- 2、 ThreadLocal是在当前线程中存取变量的
- 3、 ThreadLocal是采用Map,也就是键值对的形式进行变量存取的
我们先看看如何使用的,然后在分析分析源码,例如下面就是使用ThreadLocal的一个例子:
public class MyClass {
//定义了两个ThreadLocal分别为:local1,local2
private static ThreadLocal<String> local1 = new ThreadLocal<>();
private static ThreadLocal<Boolean> local2 = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
public static void main(String[] args) {
System.out.println("local1:" + local1.get());//运行结果:local1:null
System.out.println("local2:" + local2.get());//运行结果:local2:false
local1.set("我是local1");
local2.set(true);
System.out.println("local1:" + local1.get());//运行结果:local1:我是local1
System.out.println("local2:" + local2.get());//运行结果:local2:true
}
}
通过以上代码你会发现我在未进行任何存储操作即未调用set(T t)方法之前:local1返回的是null,local2返回的却是false;这就和local2重写了initialValue方法有关,具体请看ThreadLocal源码分析(这里剔除掉了和我们使用不相干的代码):
package java.lang;//所在包
public class ThreadLocal<T> {
... //省略掉之前的代码
//这个方法看到没是protected的,也就是说希望我们重写的,默认返回null
//初始化值,在未存储任何值的前提下,去取值返回的结果,默认返回null
protected T initialValue() {
return null;
}
//从ThreadLocalMap中获取存储的变量
public T get() {
//先获取当前的线程对象
Thread t = Thread.currentThread();
//再获取线程对象中的ThreadLocalMap成员变量:threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
//如果不为空,则以自己注意是自己为键,来获取对应的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
//如果value不为空,则直接返回
return (T)e.value;
}
return setInitialValue();
}
//往ThreadLocalMap中加入一个变量
public void set(T value) {
//先获取当前的线程对象
Thread t = Thread.currentThread();
//再获取线程对象中的ThreadLocalMap成员变量:threadLocals
ThreadLocalMap map = getMap(t);
//判断返回的ThreadLocalMap是否为空
if (map != null)
//如果不为空,则以自己注意是自己为键,value参数为值保寸到线程的ThreadLocalMap中
map.set(this, value);
else
//如果为空,则创建一个ThreadLocalMap,并以自己注意是自己为键,value参数为值,保存到该Map中同时将该Map赋值给当前线程中的ThreadLocalMap成员变量:threadLocals
createMap(t, value);
}
//移除掉ThreadLocalMap存储的变量
//该方法在jdk1.5加入
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
static class ThreadLocalMap {
//省略ThreadLocalMap的具体实现
......
......
}
}
其实我们在使用ThreadLocal进行变量存取操作的时候用到的也就是以上的几个方法:initialValue(),get(),set(),remove()。类比Map的操作就好,set就是存,get就是取,remove就是删。
这里需要注意的是ThreadLocal进行变量存储的时候都是以自身(即this)为键来进行存储的,所以存储的是set方法而不是put或add,因为一个ThreadLocal对象就是一个键,一个键在Map中只能对应一个值;你要想往当前线程的Map中存几个不同的值,那就需要几个不同的键,即要创建几个ThreadLocal对像,当然不同线程中也可以同时使用同一个ThreadLocal作为键来存取不同的值,他们之间对值的操作互不影响。打个比方:
我用同一个身份证号(同一个ThreadLocal对象),在A商场(线程A)注册了VIP,在B商场(线程B)也注册了VIP,有一天我将A商场的VIP改变为了SVIP,那我在B商场依旧是VIP而不会因为我改变了A商场的身份会影响到我B商场。
总结
ThreadLocal其实还是蛮有用的在Android的UI线程中Handler的Looper就是通过ThreadLocal存储在当前线程中的,所以你只要在UI线程中创建的Handler用的都是之前已经在该线程存储好的Looper。一些开源库其实很多都用到了ThreadLocal,例如何洪辉的EventBus,其他的这里不做过多介绍,有兴趣的可以去看看一些好的开源库的代码,收获会很大。