这个问题困扰了自己挺久,知道在知乎上看到一个大神的回答,顿时有种拨开云雾见青天的感觉。
https://www.zhihu.com/question/23089780
在前人的基础上再次稍加总结,以加深印象。
ThreadLocal并不是为了解决并发访问数据过程中可能存在的数据不一致问题,事实上ThreadLocal是为了解决变量在线程内部各个方法之间共享的问题。
class TestGoWan implements Runnable{
private int id;
public TestGoWan(int id) {
super();
this.id = id;
}
private static ThreadLocal<GongJiaoCard> tl = new ThreadLocal<GongJiaoCard>();
public void run() {
GongJiaoCard g = new GongJiaoCard((int)(Math.random()*100));
tl.set(g);
this.goByBus();
this.goByMetro();
}
public void goByBus(){
System.out.println("线程id"+id+"拿着公交卡id:"+tl.get().getId()+"坐公交!");
}
public void goByMetro(){
System.out.println("线程id"+id+"拿着公交卡id:"+tl.get().getId()+"坐地铁!");
}
}
class GongJiaoCard{
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public GongJiaoCard(int id) {
super();
this.id = id;
}
}
public class TestThread{
public static void main(String[] args) {
for(int i = 1; i<6; i++){
Thread t = new Thread(new TestGoWan(i));
t.start();
}
}
}
代码运行结果如下:
线程id4拿着公交卡id:56坐公交!
线程id2拿着公交卡id:14坐公交!
线程id2拿着公交卡id:14坐地铁!
线程id3拿着公交卡id:36坐公交!
线程id5拿着公交卡id:37坐公交!
线程id1拿着公交卡id:48坐公交!
线程id5拿着公交卡id:37坐地铁!
线程id3拿着公交卡id:36坐地铁!
线程id4拿着公交卡id:56坐地铁!
线程id1拿着公交卡id:48坐地铁!
可以看到每个线程拿的都是自己的公交卡,也就是说ThreadLocal中保存的变量在同一线程的不同方法(goByBus、goByMetro)之间可以共享,但是在不同的线程之间是隔离的。
要想实现变量在同一线程的不同方法之间共享,而在线程之间隔离貌似用如下方式也可以
class TestGoHaveFun implements Runnable{
private int id;
private GongJiaoCard g;
public TestGoHaveFun(int id) {
super();
this.id = id;
}
public void run() {
g = new GongJiaoCard((int)(Math.random()*100));
goByBus();
goByMetro();
}
public void goByBus(){
System.out.println("线程id"+id+"拿着公交卡id:"+g.getId()+"坐公交!");
}
public void goByMetro(){
System.out.println("线程id"+id+"拿着公交卡id:"+g.getId()+"坐地铁!");
}
}
这个问题暂且放下,待哪天真正弄明白再来补充。
接下来分析从ThreadLocal源码的角度分析下其实现原理。
先从ThreadLocal核心的set和get方法开始:
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是ThreadLocal的内部类。拿到这个map后,以当前的 ThreadLocal对象为键,参数value为值将此键值对保存到此map中。这样,由于这个ThreadLocalMap是通过当前线程获取的,那么保存在该map中的键值对就是当前线程相关的。