问题提出:当我们在一个多线程的环境中使用了全局变量,这个全局变量就存在一定被篡改的风险;为了避免共享变量的使用这种风险;我们提出了ThreadLocal辅助类为每一个线程提供各自的实例;
1:什么是ThreadLocal?
ThreadLocal是java.lang下面的一个类,是用来解决java多线程程序中并发问题的一种途径;通过为每一个线程创建一份共享变量的副本来保证各个线程之间的变量的访问和修改互相不影响;
2:它都有哪些方法:
public T get() { }
get方法用来获取当前先线程中的共享变量的副本
public void set(T value) { }、
set是用来设置当前线程中变量的副本值
public void remove() { }
remove是用来移除当前线程中变量的副本,回收内存;其实这个不是必须要调用的,因为当线程失效之后,这些内存会自动释放
protected T initialValue() { }
是一个protected方法;一般用于在使用时进行重写;是一个延迟加载的方法;
3:它具体是怎么为每一个线程创建一个副本的呢?
A:首先它会先调用当前线程的get方法;源码如下:
public T get() {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
这里我们在来看看getMap的源码:返回当前线程的ThreadLocalMap对象;
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap是ThreadLocal的静态内部类;
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
.....
}
可以看到Entry也是ThreadLocalMap的静态内部类;
它的构造方法传递两个参数一个是ThreadLocal一个Object;相当于键值对;
如果返回的ThreadLocalMap类中有值,获取当前对象map对象的实体entry;
如果entry不为空则返回entry的值;否则调用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;
}
protected T initialValue() {
return null;
}
如果map不为空就去设置map如果map为空就创建一个map
void createMap(Thread t, T firstValue) {
//这里的this指代的当前的ThreadLocal对象;
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
下面我们就来总结ThreadLocal具体是怎么一步一步去为每一个线程创建一个变量的副本的:
A:首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,值value为变量副本(即T类型的变量)。
B:初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
C:然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
总结一下:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
3)在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法;
4:ThreadLocal的应用场景:
数据库连接和session管理
文章大部分内容引自:
作者:海子
出处:http://www.cnblogs.com/dolphin0520/