ThreadLcoal源码解析

本文分如下几个部分进行来对ThreadLocal进行学习:

1、什么是ThreadLocal?

2、 Threadlcoal的组成部分和数据结构

3、ThreadLocal主要方法分析

4、ThreadLocal为啥会有内存泄漏,如何避免?

我们从第一个开始

1、什么是ThreadLocal?

我们通过一个简单的demo来看下

public class MyClass {
    private static Integer num = 0;

    public static void main(String[] args) {

        Thread[] threads = new Thread[5];
        for (int x =0;x<threads.length;x++){
            threads[x] = new Thread(new Runnable() {
                @Override
                public void run() {
                    num+=5;

                }
            }

            );
        }

 for (int x =0;x<threads.length;x++){
            threads[x] .start();
        }
    }
}

输出结果为:

09-10 15:11:08.679 6630-6644/= E/Thread: Thread-4996--5
09-10 15:11:08.679 6630-6645/ E/Thread: Thread-4997--10
09-10 15:11:08.679 6630-6646/ E/Thread: Thread-4998--15
09-10 15:11:08.684 6630-6647/ E/Thread: Thread-4999--20
09-10 15:11:08.684 6630-6648/ E/Thread: Thread-5000--25

我们将代码改造下,加入ThreadLcoal

public class MyClass {
    private static Integer num = 0;
   ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>(){
        @Nullable
        @Override
        protected Integer initialValue() {
            return num ;
        }
    };


    public static void main(String[] args) {

        Thread[] threads = new Thread[5];
        for (int x =0;x<threads.length;x++){
            threads[x] = new Thread(new Runnable() {
                @Override
                public void run() {
                      int i = integerThreadLocal.get().intValue();
                        i+=5;
                        integerThreadLocal.set(i);
                        Log.e("Thread",Thread.currentThread().getName()+"--"+
                                +integerThreadLocal.get());

                }
            }

            );
        }

 for (int x =0;x<threads.length;x++){
            threads[x] .start();
        }
    }
}

输出结果为:

09-10 15:31:41.084 8415-8449/? E/Thread: Thread-5044--5
09-10 15:31:41.084 8415-8450/? E/Thread: Thread-5045--5
09-10 15:31:41.084 8415-8452/? E/Thread: Thread-5046--5
09-10 15:31:41.084 8415-8454/? E/Thread: Thread-5047--5
09-10 15:31:41.084 8415-8455/? E/Thread: Thread-5048--5


09-10 15:31:47.459 8639-8656/ E/Thread: Thread-5047--5
09-10 15:31:47.459 8639-8657/ E/Thread: Thread-5048--5
09-10 15:31:47.459 8639-8658/ E/Thread: Thread-5049--5
09-10 15:31:47.459 8639-8659/ E/Thread: Thread-5050--5
09-10 15:31:47.459 8639-8660/ E/Thread: Thread-5051--5

我们发现这次每个线程对应的变量 ,互不相互干扰

那什么是ThreadLcoal? 为共享变量在每个线程中创建一个副本,每个线程可以访问自己的内部的副本变量

2、 Threadlcoal的组成部分和数据结构

本来想自己画张图的,网络上有个比较经典的就拿来吧,如果有侵权,可以联系删除

从上图我们可以看到这些相关的类

Thread 类
成员变量:ThreadLocal.ThreadLocalMap threadLocals 
class Thread implements Runnable {
 .....
    ThreadLocal.ThreadLocalMap threadLocals = null;
.....
}

ThreadLocal类 

静态内部类: ThreadLocalMap 

静态内部类: Entry  是一个继承WeakReference的,一个hash表结构的静态内部类

public class ThreadLocal<T> {  
............
static class ThreadLocalMap {

     ............
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
............

          }

............
     }

3、ThreadLocal主要方法分析

3.1 initialValue()方法;

 protected T initialValue() {
        return null;
    }

该方法的访问修饰符是protected,该方法为第一次调用get方法提供一个初始值。默认情况下,第一次调用get方法返回值null。在使用时,我们一般会重写ThreadLocal的initialValue方法,返回一个我们设定的初始值

3.2 set(T value)方法;

1.public void set(T value)设置当前线程的线程局部变量的值。

/**
 *  设置ThreadLocal变量的值
 */
public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap 
    ThreadLocalMap map = getMap(t);
    // 如果当前线程的ThreadLocalMap非空
    if (map != null)
        // 往ThreadLocalMap中添加K-V  (2)详细
        map.set(this, value);
    else
        // 如果当前线程的ThreadLocalMap为空    (1)详细
        // 创建ThreadLocalMap对象
        createMap(t, value);
}



/**   
     (1)详细
 *   查看createMap(t, value);
 */
void createMap(Thread t, T firstValue) {
//创建t内部的ThreadLocalMap并将firstValue存入
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }




/**
*    (2)详细
 *   查看createMap(t, value);
 */

   private void set(ThreadLocal<?> key, Object value) {

              //1 这里和我们的hashMap非常像,算出下标

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            //遍历hash表
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
            //覆盖之前的值
                if (k == key) {
                    e.value = value;
                    return;
                }
            //如果当前遍历发现key为null则要重新计算位置,并且将null清除,这是因为我们的key是弱/引用的

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
//如果上面的条件都不成立,则新增一个Entry,并判断是否要扩容
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

3.3 get()方法

 public T get() {
        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);
        if (map != null) {
                //不为null的时候返回  详细看1
            ThreadLocalMap.Entry e = map.getEntry(this);  
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
//否则返回默认值
        return setInitialValue();
    }



         
/**  1分析 getEntry
   *
   */
  private Entry getEntry(ThreadLocal<?> key) {
      // key的hashcode & 1111,即保留key的hashcode的低4位
      int i = key.threadLocalHashCode & (table.length - 1);
      // 获取hash表位置i处的Entry对象
      Entry e = table[i];
      // e非空且e的key等于key,说明就是要查找的Entry对象
      if (e != null && e.get() == key)
          // 返回Entry对象e
          return e;
      else
          // 没有找到对应的Entry,需要继续查找  分析2
          return getEntryAfterMiss(key, i, e);
  }





/**
    *分析2  getEntryAfterMiss();
   * 直接在hash表对应的槽位上没有找到对饮的Entry
   * 继续在hash表上查找
   */
  private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
      // hash表的Entry数组
      Entry[] tab = table;
      // Entry数组的长度
      int len = tab.length;
    // 如果e非空
      while (e != null) {
          // 获取e的key
          ThreadLocal<?> k = e.get();
          // 如果k等于key,找到对应的Entry
          if (k == key)
             // 返回Entry对象
              return e;
          // 如果k为空
          if (k == null)
              // 移除这个失效的Entry对象
              expungeStaleEntry(i);
          else
              // 修改i为hash表的下一个位置
              i = nextIndex(i, len);
          // 修改e为tab[i],进入下一次循环
          e = tab[i];
      }
      // 没有找到对应的Entry对象
      return null;
  }

分析  expungeStaleEntry(i);


/**
   * 清理失效的Entry
   */
  private int expungeStaleEntry(int staleSlot) {
      Entry[] tab = table;
      int len = tab.length;

      // 清理staleSlot位置的Entry
      tab[staleSlot].value = null;
      tab[staleSlot] = null;
      size--;

      // 调整hash表直到后面第一个tab[i]为null为止
      Entry e;
      int i;
      for (i = nextIndex(staleSlot, len);
           (e = tab[i]) != null;
           i = nextIndex(i, len)) {
          ThreadLocal<?> k = e.get();
          // 如果key为null,将该entry置为null,注意 我们这里也将value也是设置成null
          if (k == null) {
              e.value = null;
              tab[i] = null;
              size--;
          } else {
              // 下面是新计算一下hash值,如果位置与当前位置不同
        // 需要重新找一个位置放该节点。用的也是线性探测法
              int h = k.threadLocalHashCode & (len - 1);
              if (h != i) {
                  tab[i] = null;

                  // Unlike Knuth 6.4 Algorithm R, we must scan until
                  // null because multiple entries could have been stale.
                  while (tab[h] != null)
                      h = nextIndex(h, len);
                  tab[h] = e;
              }
          }
      }
      return i;
  }

4、ThreadLocal为啥会有内存泄漏,如何避免?

这里还是上一张网络图片,如有版权联系删除

为什么会有内存泄漏?

在上图我们可以看到清晰一个引用链,ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。

如何解决?

其实我们在上面源码分析中已经看到了,在ThreadLocal的get()、set()调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,将这个潜在的引用链切断了。

我们也可以在使用完毕ThreadLocal,手动的调用ThreadLocal的remove()方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值