Threadlocal源码分析以及其中WeakReference作用分析

今天在看Spring 3.x企业应用开发实战,第九章 Spring的事务管理,9.2.2节ThreadLocal的接口方法时,书上有提到Threadlocal的简单实现,我就去看了下JDK1.8的Threadlocal的源码。发现实现方式与书中讲的并不相同,同时在网上搜索了一下,发现有比较多的人理解错了。

先看一下容易误导的解释:在ThreadLocal类中有一个Map对象,这个Map以每个Thread对象为键,保存了这个线程对应局部变量值,对应的实现方式如下:

  1. public class SimpleThreadLocal {
  2. private Map valueMap = Collections.synchronizedMap(new HashMap());
  3. public void set(Object newValue) {
  4. valueMap.put(Thread.currentThread(), newValue);//①键为线程对象,值为本线程的变量副本
  5. }
  6. public Object get() {
  7. Thread currentThread = Thread.currentThread();
  8. Object o = valueMap.get(currentThread);//②返回本线程对应的变量
  9. if (o == null && !valueMap.containsKey(currentThread)) {//③如果在Map中不存在,放到Map 中保存起来。
  10. o = initialValue();
  11. valueMap.put(currentThread, o);
  12. }
  13. return o;
  14. }
  15. public void remove() {
  16. valueMap.remove(Thread.currentThread());
  17. }
  18. public Object initialValue() {
  19. return null;
  20. }
  21. }

为什么不按照那种有误的方法实现呢?
看起来似乎是不同线程获取了各自的值,但是这些值并没有线程独立。线程A可以操作线程B对应的值。如果某个线程将保存这些值的Map置为null了,那么其他线程也无法访问了。

实际上是怎样的呢
我们看ThreadLocal的get()方法源码。可以看到获取Threadlocal中的值,是先通过当前线程的线程对象t,获取t的ThreadlocalMap属性对象,然后再以Threadlocal对象为键,去获取ThreadlocalMap中的值。

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);//获取线程对象的属性
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);//这里的this是指Threadlocal对象,Threadlocal在类中通常以静态属性出现,所以多个线程的Threadlocal指向同一个对象。
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }
  14. ThreadLocalMap getMap(Thread t) {
  15. return t.threadLocals;
  16. }

同时我们查看ThreadLocal源码中定义的静态类ThreadLocalMap,其实底层封装的是一个Entry数组,获取方式和普通的HashMap不太一样,如果没有命中,就直接通过线性搜索,因为ThreadLocalMap需要保存的Entry并不会太多。

  1. private Entry getEntry(ThreadLocal<?> key) {
  2. int i = key.threadLocalHashCode & (table.length - 1);
  3. Entry e = table[i];
  4. if (e != null && e.get() == key)
  5. return e;
  6. else
  7. return getEntryAfterMiss(key, i, e);
  8. }
  9. private void set(ThreadLocal<?> key, Object value) {
  10. // We don't use a fast path as with get() because it is at
  11. // least as common to use set() to create new entries as
  12. // it is to replace existing ones, in which case, a fast
  13. // path would fail more often than not.
  14. Entry[] tab = table;
  15. int len = tab.length;
  16. int i = key.threadLocalHashCode & (len-1);
  17. for (Entry e = tab[i];
  18. e != null;
  19. e = tab[i = nextIndex(i, len)]) {
  20. ThreadLocal<?> k = e.get();
  21. if (k == key) {
  22. e.value = value;
  23. return;
  24. }
  25. if (k == null) {
  26. replaceStaleEntry(key, value, i);
  27. return;
  28. }
  29. }

通过ThreadLocal,每个线程保存自身的数据,不能访问到其他线程的数据。

ThreadLocalMap的Entry使用ThreadLocal的WeakReference引用作为Key值,当所有线程运行出ThreadLocal的作用域时,即没有强引用ThreadLocal时,ThreadLocal就会被回收。

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. /** The value associated with this ThreadLocal. */
  3. Object value;
  4. Entry(ThreadLocal<?> k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }

不是Threadlocal为每个线程提供了独立的变量,而是每个线程自己带了自己独立的变量。

关于内存泄漏
关于ThreadLocalMap的内存泄漏:如果一个ThreadLocal的生命周期结束,即在ThreadLocal所处的类中没有了强引用,而Thread没有结束,在Thread的threadLocals成员变量中,会有一个Entry使用弱引用引用了ThreadLocal作为key,因为是弱引用,这个key将被回收。而value是强引用,看起来是会造成泄漏,但是在ThreadLocalMap的set和get方法中,有一些释放的方法。具体的我也不太懂。
还是老老实实使用ThreadLocal的remove方法比较好。

  1. public void remove() {
  2. ThreadLocalMap m = getMap(Thread.currentThread());
  3. if (m != null)
  4. m.remove(this);
  5. }


来自为知笔记(Wiz)


posted on 2016-04-11 21:43  Frank_Hao  阅读(587)  评论(0编辑  收藏  举报

导航