我们知道ThreaLocal是线程的副本,每个线程都持有各自的ThreadLocal副本,互不干扰,那么又怎么会有线程安全问题呢?
一. 先来一个实验案例
/**
* @author charles
* @createTime 2020/6/7 14:39
* @description threadLocal 可能产生的线程不安全因素测试
*/
public class ThreadLocalUnSafe extends Thread{
private static Company company = new Company(0);
private static ThreadLocal<Company> threadLocal = new ThreadLocal<Company>();
@Override
public void run() {
// 每个线程都进行 + 1 操作
company.setAge(company.getAge() + 1);
// 存储到ThreadLocal中
threadLocal.set(company);
try {
// 休眠可以使结果差异更容易看到
Thread.sleep(1);
} catch (InterruptedException e) {
//todo
}
System.out.println(Thread.currentThread().getName() + "---" + threadLocal.get().getAge());
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new ThreadLocalUnSafe().start();
}
}
static class Company{
private int age;
public Company(int i) {
}
public Company() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
结果:
二. 结果分析
这个测试程序启用了5个子线程,本意是各个线程操作各自的ThreadLocal中持有的Company。但是,显然这个结果与我们的初衷相悖。可是这又是为什么呢?ThreadLocal不是各自持有自己的变量副本吗?
这个原因在于我们使用的new Company(0) 是全局的静态变量,全局共享的。而当我们查看ThreadLocalMap时,不难发现,其存储的是一个对象的引用。当线程休眠时,一个线程改变了这个共享变量company时,也将会影响到其他线程持有的此变量,因此会出现线程不安全的问题。
如果我们使用如下方法修改ThreadLocal初始化,重新new一个对象呢?
private static ThreadLocal<Company> threadLocal = new ThreadLocal<Company>(){
@Override
protected Company initialValue() {
return new Company();
}
};
测试结果:
重新new了对象也是不行,继续寻找原因:ThreadLocal在初始化的时候调用的initialValue()方法时,初始化了一个null,当调用get方法时,才会调用initialValue()方法初始化值。而threadLocal调用的set()方法set进去的对象时全局变量company,因此此方法显然无效。
实际上我们只需要去掉new Company(0)的static关键字修饰即可,这样每个ThreadLocal都是持有各自的Company。就不会出现线程安全问题了。