ThreadLocal源码阅读
什么是ThreadLocal
ThreadLocal 是 Java 里一种特殊变量,它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量。用于线程内共享
使用demo
static ThreadLocal threadLocal=new ThreadLocal(); public static void main(String[] args) { //<1>set threadLocal.set("dd"); test(); } public static void test(){ //线程内共享 打印dd System.out.println(threadLocal.get()); }
ThreadLocal
<1>set方法
java.lang.ThreadLocal#set
public void set(T value) { //获得当前线程对象 Thread t = Thread.currentThread(); //<3>获得t的threadLocals的成员变量 也就是ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) //<4>ThreaLocal为key value为我们的值 map.set(this, value); else //为t初始化一个ThreaLocalMap并设置值 createMap(t, value); }
<3>getMap
java.lang.ThreadLocal#getMap
//返回ThreadLocal.ThreadLocalMap ThreadLocal.ThreadLocalMap getMap(Thread t) { //获得Thread的成员变量threadLocals return t.threadLocals; }
从上面可以看出我们的数据都是存储在ThreadLocalMap,ThreadLocalMap就是一个普通的map通过ThreadLocal对象为key
<4>set
java.lang.ThreadLocal.ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. //ThreaLocalMap内部通能数组来保存 通过Entry封装 Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; //threadLocalMap是通过线性探测法来解决hash碰撞,当出现hash碰撞查找数组的下一个位置 e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } //<5>通过内部内Entry 来存储 key为ThreadLocal value为我们set的值 并存储到数组的具体位置 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
<5>Entry
/** * ThreadLocalMap静态内部类 * WeakReference 为弱引用,当弱引用引用的的对象没有被强引用时也会被回收 * 这里可以发现 value是强引用 * super(k) key为ThreadLocal 传给了父类 则是弱引用 */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
<6>get方法
public T get() { //获得当前线程 Thread t = Thread.currentThread(); //<3>获得线程map ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) { //根据当前threadLoca 找到对应的entry ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { //获得value @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果为null则触发初始化 return setInitialValue(); } private T setInitialValue() { //初始化 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocal.ThreadLocalMap map = getMap(t); //如果map不为null if (map != null) //直接set进去 map.set(this, value); else //创建map设置进去 createMap(t, value); return value; } /** * 默认为空实现 我们可以继承重写达到默认值的效果 * @return */ protected T initialValue() { return null; }
ThreadLocal内存泄露
或者我们使用线程池技术,没有及时remove导致Threadlocal常驻到Thread
根据<5>处我们可以看到ThradLocal没有被强引用时就会被垃圾回收 即Entry Key会被回收,但是value还被Entry引用 ,Entry被ThreadLocalMap引用 ThreadLocalMap被Thread引用,所以在线程未被回收前对象会一直不会被回收,如果使用线程池技术 可能导致内存泄露
如:
public static void main(String[] args) { test(); } public static void test(){ ThreadLocal threadLocal=new ThreadLocal(); threadLocal.set(new Student());; }
弱引用Entry的keyThreadLocal对象会被回收,但是Entry还持有value的引用 ThreadLocalMap持有Entry引用 Thread持有ThreadLocalMap引用 导致内存泄露,所以当使用完要手动调用remove方法
public static void test(){ ThreadLocal threadLocal=new ThreadLocal(); threadLocal.set("ddd");; threadLocal.remove(); }
key使用弱引用是下次使用Thread.remove方法会回收key为null的
private void remove(ThreadLocal<?> key) { //使用hash方式,计算当前ThreadLocal变量所在table数组位置 Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //再次循环判断是否在为ThreadLocal变量所在table数组位置 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //调用WeakReference的clear方法清除对ThreadLocal的弱引用 e.clear(); //清理key为null的元素 expungeStaleEntry(i); return; } } }
总结
1.ThreadLocal本质获取当前线程对象的成员变量ThreadLocalMap ThreadLocalMap通过ThreadLocal作为key存储相应的value value通过Entry封装
2.使用map的原因是因为一个线程可以定义多个ThreadLocal