ThreadLocal可以理解为主要解决多线程并发的问题,实际上,在使用场合上也多是在处理多线程并发的时候会用到ThreadLocal这个类。然而ThreadL并不是一个Thread,而是代表了线程变量的副本。ThreadLocal只对外开放了四个方法,分别是构造器ThreadLocal()、set()、get()、和remove(),当然,还有一个initialValue()方法是protected类型,这个方法应该是提供给开发人员去重写的。
ThreadLocal的使用方式一般是创建Threadlocal对象,然后调用他的方法。
ThreadLocal local=new ThreadLocal();
看一下set()的源码:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap,也就是Thread的threadLocals 变量
ThreadLocalMap map = getMap(t);
//如果不为空就将value放进去,为空就创建一个map
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
源码中的Thread的threadLocals 变量
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap可以看成一个HashMap,键为当前ThreadLocal的对象,值为我们要放入的值
getMap()的源码:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到直接返回的是当前线程的threadLocals变量
createMap()的源码:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
以上就是ThreadLocal的赋值操作的原理,现在来看一下get()方法的源码:
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取 ThreadLocalMap的实例
ThreadLocalMap map = getMap(t);
//如果不为空就调用 ThreadLocalMap的实体,否则就调用 setInitialValue()方法
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
看一下 setInitialValue()的源码:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
可以看到 setInitialValue()是调用initialValue();生成一个初始值。
而initialValue()的源码:
protected T initialValue() {
return null;
}
initialValue()直接返回了null,所以我们在创建ThreadLocal对象时,最好重写一下 initialValue()方法,设置一个默认初始值,在没有调用set()的情况下,避免出现空指针异常。
ThreadLocal的应用场景
先看一下不使用ThreadLocal,多线程下操作共享变量
private static int Count=0;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<5;i++){
Count=Count+1;
System.out.println(Thread.currentThread().getName()+":"+Count);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<5;i++){
Count=Count+1;
System.out.println(Thread.currentThread().getName()+":"+Count);
}
}
}).start();
}
运行结果:
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10
Process finished with exit code 0
可以看到这两个线程都使用了共享变量,但是没有处理多线程并发问题的处理方法,所以才会不符合我们的预期值。
使用ThreadLocal,多线程下操作共享变量:
public static void main(String[] args) {
ThreadLocal local=new ThreadLocal(){
protected Integer initialValue(){
return 0;
}
};
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<5;i++){
local.set(local.get()+1);
System.out.println(Thread.currentThread().getName()+":"+local.get());
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<5;i++){
local.set(local.get()+1);
System.out.println(Thread.currentThread().getName()+":"+local.get());
}
}
}).start();
}
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Process finished with exit code 0
ThreadLocal和加锁的方式的比较
ThreadLocal和加锁的方式都是为了解决多线程对共享变量的并发问题
在加锁的方式中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线 程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
ThreadLocal的使用场景:
当 然ThreadLocal并不能替代同步机制,两者面向的问题领域不同。同步机制是为了 同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信的 有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线 程之间共享资源(变量),这样当然不需要对多个线程进行同步了。所 以,如果你需要进行多个线程之间进行通信,则使用同步机制;如果需要隔离多个线程之间 的共享冲突,可以使用ThreadLocal,这将极大地简化你的程 序,使程序更加易读、简洁。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。