注意:调试JDK源码,无法准确查看变量,必须重新编译rt.jar,具体方法在这里贴出Java中ThreadLocal类的说明Doc:这个类提供线程局部变量。这些变量与它们的普通对应变量的不同之处在于,每个访问一个变量(通过其get或set方法)的线程都有其自己的、独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户ID或事务ID)。此类提供线程局部变量。这些变量与普通变量的不同之处在于,每个访问一个变量(通过其get或set方法)的线程都有其自己独立初始化的变量副本。ThreadLocal实例通常是私有静态字段,并且希望将状态与线程相关联(例如,用户ID或事务ID)。我们先分析下面的代码:publicclassThreadTest{publicstaticvoidmain(String[]args){MyThreadthread=newMyThread();thread.start();//thread.join();//3、省略了异常处理语句System.out.println(thread.get());//1}}classMyThreadextendsThread{privateThreadLocalvalue=newThreadLocal<>();publicStringget(){返回值.get();}@Overridepublicvoidrun(){value.set("hello");//2}}这段代码的输出可能和你预期的不一样,结果不是hello而是null。至于为什么,我们通过分析Java源码和Debug查看变量来理解这个过程。ThreadLocal.get()方法如下:publicTget(){Threadt=Thread.currentThread();//注意这里ThreadLocalMapmap=getMap(t);//ThreadLocal.ThreadLocalMapif(map!=null){ThreadLocalMap.条目e=map.getEntry(this);//key是当前的ThreadLocal实例if(e!=null){@SuppressWarnings("unchecked")Tresult=(T)e.value;返回结果;}}returnsetInitialValue();}从上面可以看出,这个方法首先获取的是当前正在执行的线程。每个线程都有一个名为ThreadLocals的变量,类型为ThreadLocal.ThreadLocalMap。通过ThreadLocal保存的数据其实就是保存在这个变量中。get数据的Key值为this指向的ThreadLocal实例。下面是ThreadLocal.set()方法如下:publicvoidset(Tvalue){Threadt=Thread.currentThread();//与get()方法相同ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);elsecreateMap(t,value);}set()方法也是先获取当前正在执行的线程,然后获取线程的ThreaLocalMap变量,然后赋值。我们提到第一块代码的结果是null而不是hello。首先,程序启动时会产生两个线程,一个是主线程,执行main()方法,另一个是我们的MyThread实例,默认名字是Thread-0。所以因为线程并行,代码1会先于代码2执行,所以get()先于set()是结果为null的原因吗?不,只是部分原因。我们取消代码3的注释,结果仍然为空。下面我通过Debug来展示这个过程。因为有thread.join()方法,代码先执行到代码2,给ThreadLocal变量赋值。进入ThreaLocal.set()方法,可以看到当前线程t为Thread-0。也就是说,值“hello”存储在MyThread实例的threadLocals中。继续代码1,进入ThreadLocal.get()方法,会发现当前线程t指向的线程为主线程。现在,我们可以理解为什么第一段代码的执行结果是null而不是hello了。因为当我们调用set()方法赋值时,这个值是存放在Thread-0中的,而get()中的值是从主线程中取的(因为是在main()方法中调用的),main线程的ThreadLocal变量没有初值,也没有赋值,所以输出结果为null。所以这就是JavaDoc提到的ThreadLocal的特性:每个线程都有自己独立初始化的变量副本。main()方法所在的主线程也不例外。