从字面上理解ThreadLocal就是“线程局部变量”的意思。简单的说就是,一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)。
学习一个类之前我们需要了解一个类的API,这也是我们学习类的入口。而ThreadLocal类的API相当简单。
在这里面比较重要的就是,get、set、remove了,这三个方法是对这个变量进行操作的关键。set用于赋值操作,get用于获取变量中的值,remove就是删除当前这个变量的值。需要注意的是initialValue方法会在第一次调用时被触发,用于初始化当前变量值,例如在下列代码中我们需要创建一个ThreadLocal,用于创建一个与线程绑定的Connection对象:
ThreadLocal connection = new ThreadLocal(){
public Connection initialValue(){
return DriverManager.getConnection(…);
}
});
为什么我们将ThreadLocal说成变量,我们姑且可以这么理解,每个ThreadLocal实例中都可以保存一个值(基本数据类型值或者引用类型的引用值),而内部保存的值是可以修改的,而这样的特性与变量的特性及其相似,变量不就是用来保存一个值的吗?也就是说每一个ThreadLocal实例就类似于一个变量名,不同的ThreadLocal实例就是不同的变量名,它们内部会存有一个值(暂时这么理解)在后面的描述中所说的“ThreadLocal变量或者是线程变量”代表的就是ThreadLocal类的实例。我们通过重写initialValue方法指定ThreadLocal变量的初始值,默认情况下initialValue返回的是null。
接下来我们就来动手实践一下,来理解前面没有理解的那句话:一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值)
public class ThreadLocalTest {
private static ThreadLocal<Integer> num = new ThreadLocal<Integer>() {
// 重写这个方法,可以修改“线程变量”的初始值,默认是null
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
// 创建一号线程
new Thread(new Runnable() {
@Override
public void run() {
// 在一号线程中将ThreadLocal变量设置为1
num.set(1);
System.out.println("一号线程中ThreadLocal变量中保存的值为:" + num.get());
}
}).start();
// 创建二号线程
new Thread(new Runnable() {
@Override
public void run() {
num.set(2);
System.out.println("二号线程中ThreadLocal变量中保存的值为:" + num.get());
}
}).start();
//为了让一二号线程执行完毕,让主线程睡500ms
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("主线程中ThreadLocal变量中保存的值:" + num.get());
}
}
上面的代码中在类中创建了一个静态的“ThreadLocal变量”,在主线程中创建两个线程,在这两个线程中分别设置ThreadLocal变量为1和2。然后等待一号和二号线程执行完毕后,在主线程中查看ThreadLocal变量的值。
程序结果及分析:
程序结果重点看的是主线程输出的是0,如果是一个普通变量,在一号线程和二号线程中将普通变量设置为1和2,那么在一二号线程执行完毕后在打印这个变量,输出的值肯定是1或者2(到底输出哪一个由操作系统的线程调度逻辑有关)。但使用ThreadLocal变量通过两个线程赋值后,在主线程程中输出的却是初始值0。在这也就是为什么“一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的”,每个线程都只能看到自己线程的值,这也就是ThreadLocal的核心作用:实现线程范围的局部变量。
我们还是将最后结论摆在前面,每个Thread对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。也就是说,想要存入的ThreadLocal中的数据实际上并没有存到ThreadLocal对象中去,而是以这个ThreadLocal实例作为key存到了当前线程中的一个Map中去了,获取ThreadLocal的值时同样也是这个道理。这也就是为什么ThreadLocal可以实现线程之间隔离的原因了。
总结:
1)ThreadLocal的作用:实现线程范围内的局部变量,即ThreadLocal在一个线程中是共享的,在不同线程之间是隔离的。
2)ThreadLocal的原理:ThreadLocal存入值时使用当前ThreadLocal实例作为key,存入当前线程对象中的Map中去。最开始在看源码之前,我以为是以当前线程对象作为key将对象存入到ThreadLocal中的Map中去。
两者都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
如果ThreadLocal没有外部强引用,那么在发生垃圾回收的时候,ThreadLocal就必定会被回收,而ThreadLocal又作为Map中的key,ThreadLocal被回收就会导致一个key为null的entry,外部就无法通过key来访问这个entry,垃圾回收也无法回收,这就造成了内存泄漏
解决方案:每次使用完ThreadLocal都调用它的remove()方法清除数据,或者按照JDK建议将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。