本章我们将讨论ThreadLocal的使用,主要分以下几个章节:
.1)ThreadLocal是什么?
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
它并不是一个Thread,而是threadlocalvariable(线程局部变量,也叫线程本地变量)。
.2)ThreadLocal的使用
啥也不说,先看个例子
创建一个Person对象
public class Person {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ThreadLocal处理:
public class PersonManager {
private static ThreadLocal<Person> personContor = new ThreadLocal<Person>() {
@Override
protected Person initialValue() {
Person p = new Person();
p.setAge(19);
p.setName("xgj");
return p;
}
};
private static Person getPerson(){
return personContor.get();
}
@SuppressWarnings("unused")
private void setPerson(Person p){
personContor.set(p);
}
public static void main(String[] args) throws InterruptedException {
Thread p1=new Thread(){
@Override
public void run() {
Person p=PersonManager.getPerson();
p.setAge(20);
System.out.println("Person["+p.getAge()+","+p.getName()+"]");
}
};
Thread p2=new Thread(){
@Override
public void run() {
Person p=PersonManager.getPerson();
p.setAge(21);
System.out.println("Person["+p.getAge()+","+p.getName()+"]");
}
};
Thread p3=new Thread(){
@Override
public void run() {
Person p=PersonManager.getPerson();
System.out.println("Person["+p.getAge()+","+p.getName()+"]");
}
};
System.out.println(PersonManager.getPerson().getAge());
p1.start();
p2.start();
p3.start();
p1.join();
p2.join();
p3.join();
System.out.println(PersonManager.getPerson().getAge());
}
}
执行结果:
19
Person[20,xgj]
Person[21,xgj]
Person[19,xgj]
19
从结果我们可以看出,Main函数在线程p1,p2,p3执行前后Person对象的age值不变,p1,p2,p3获取Person值后修改了age的值,但是3个线程之间互不影响,操作的都是当前线程绑定的person对象。
那么ThreadLocal的原理具体是什么呢?
我们先来看看ThreadLocal的源码提供的几个方法
.1)set(T value)
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
从这个方法中,首先获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。我们再来看看ThreadLocalMap这个对象,ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,我们可以发现,map.set(this,value),这里的this指向的就是ThreadLocal对象,而值就是你所设置的对象了。我们来看一下getMap和createMap方法的实现
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;
而createMap实现为:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我们还注意到ThreadLocalMap代码中有弱引用,如下:
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
大家请注意,之所以用的是弱引用,就是为了在ThreadLocal失去强引用的时候,TreadLocal对应的Entry能够在下次gc时被回收,回收后的空间能够得到复用,在一定的程度下能够避免内存泄露。
.2)T 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();
}
get方法通过当前线程获取到ThreadLocalMap,如果ThreadLocalMap不为空,就根据ThreadLocal key从ThreadLocalMap中获取value值,如果ThreadLocalMap不存在,则调用初始化方法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;
}
在此方法中基本上跟set(T value)方法相呼应,请注意这行代码 T value =initialValue(); 这里调用了一个方法 initialValue(),我们下面来分析一下。
.3)initialValue()
来看一下源码实现,
protected T initialValue() {
return null;
}
本方法返回null,说明该方法应该由ThreadLocal子类重写,也就是有ThreadLocal实现类实现T的赋值。
通过上面几个方法的分析,我们基本上了解了ThreadLocal的工作原理,每个线程都拥有一个ThreadLocalMap对象,其中ThreadLocal对象为key,value为线程共享对象的副本,这样没有线程访问的都是本线程的变量副本,从而实现了线程间的访问隔离。
.4)remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
将当前线程局部变量的值删除,目的是为了减少内存的使用,需要注意的是,当线程结束后,对应线程的局部变量会被垃圾自动回收,所以显示调用该方法进行线程局部变量并不是必须的操作,但是能加快内存回收速度。