-------
对不起啦,对java的静态内部类理解不足,下面的图对ThreadLocalMap的引用的画面可能问题,请大家指正!我会总结大家的意见修改后形成最终结论!非常感谢!
-------
针对ThreadLocal的源代码,我画了这样一张图,并把其set和get方法的伪代码写出来,相信你结合源码一下就能明白了:
先简单看看ThreadLocal源码:(在后面会为大家上图说明,并给出一个ThreadLocal简单用法)
public class ThreadLocal<T> { 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(); } ...... ThreadLocalMap getMap(Thread t) { return t.threadLocals; } ...... public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ...... } public class Thread implements Runnable { ...... ThreadLocal.ThreadLocalMap threadLocals = null; ...... }
总结的图如下:
从图中可以看到,每个thread中都存在一个map,对此map作几下以点总结:
1、由于map是属于当前线程的,自然在多线程环境中,其每一分ThreadLocal类型的变量的拷贝都是独立的,也就是线程安全的。这也是采用ThreadLocal变量与采用线程同步方式解决线程安全的最大区别——采用线程同步方式,类的成员变量作为多线程之间的共享资源;而采用ThreadLocal变量,根本就没有想到到共享,也不可能共享。
2、map的类型是ThreadLocal的内部类ThreadLocalMap,为什么是内部类呢?这是因为该map要使用其外部类ThreadLocal的this指针作为key,这样可以保证同一个ThreadLocal类型的变量的拷贝只有唯一一份。另外注意个Map中的key是Threadlocal实例的弱引用,为什么使用弱引用呢?这个在后面为大家略作分析。
3、ThreadLocalMap是ThreadLocal的静态内部类,为什么是静态呢?
threadLocals是定义在Thread类中的,如果不是静态内部类,那Thread类中定义这个变量还需要得到ThreadLocal类的实例,这是很不方便的。可能还有其它原因(没深入研究)。
另外,由于ThreadLocalMap是通过ThreadLocal变量的this指针作为key的,因此如果ThreadLocal变量不是同一个对象,那么拿到的ThreadLocal变量就是另一份拷贝。(后面的示例代码中会做简要说明)。
4、因为ThreadLocalMap是独立于这个ThreadLocal变量的,所以在一个类中可以声明、定义多个ThreadLocal变量,这些ThreadLocal变量都会被确保是独立于线程的拷贝。
下面看一个例子:
public class TestThreadLocal { ThreadLocal<Long> longLocal = new ThreadLocal<Long>(); ThreadLocal<String> stringLocal = new ThreadLocal<String>(); public void set() { //key就是ThreadLocal变量自身(this) System.out.println("set longLocal: " + "key=" + longLocal + ",value=" + Thread.currentThread().getId()); longLocal.set(Thread.currentThread().getId()); System.out.println("set stringLocal: "+ "key=" + stringLocal + ",value=" + Thread.currentThread().getName()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { System.out.print("getLongLocal: "); return longLocal.get(); } public String getString() { System.out.print("getStringLocal: "); return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final TestThreadLocal test = new TestThreadLocal(); printThreadName(); test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { printThreadName(); test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); Thread thread2 = new Thread(){ TestThreadLocal test2 = new TestThreadLocal(); public void run() { printThreadName(); test2.set(); System.out.println(test2.getLong()); System.out.println(test2.getString()); }; }; thread2.start(); thread2.join(); printThreadName(); System.out.println(test.getLong()); System.out.println(test.getString()); } private static void printThreadName(){ System.out.println("-------------------"); System.out.println("[IN Thread " + Thread.currentThread().getName() + "]"); } }
运行结果如下:
可以明显看出:main线程和Thread-0采用的key都是同一对象,因此在两个线程中使用同一key但保存了独立于线程的不同拷贝;Thread-1由于是重新new出来的TestThreadLocal,因此采用的key则是不同的key,所以取出来的是另一份拷贝。(因此如果要取到的是同一对象,必须要求其key是同一对象)
-------------------
[IN Thread main]
set longLocal: key=java.lang.ThreadLocal@11671b2,value=1
set stringLocal: key=java.lang.ThreadLocal@12452e8,value=main
getLongLocal: 1
getStringLocal: main
-------------------
[IN Thread Thread-0]
set longLocal: key=java.lang.ThreadLocal@11671b2,value=8
set stringLocal: key=java.lang.ThreadLocal@12452e8,value=Thread-0
getLongLocal: 8
getStringLocal: Thread-0
-------------------
[IN Thread Thread-1]
set longLocal: key=java.lang.ThreadLocal@1e4f7c2,value=9
set stringLocal: key=java.lang.ThreadLocal@145f0e3,value=Thread-1
getLongLocal: 9
getStringLocal: Thread-1
-------------------
[IN Thread main]
getLongLocal: 1
getStringLocal: main