ThreadLocal原理分析
一、概述
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。
二、实现原理
ThreadLocal相关UML类图:
由该图可知,Thread类中有threadLocals和inheritableThreadLocals两个ThreadLocal.ThreadLocalMap类型的变量,二ThreadLocal.ThreadLocalMap是一个定制化的HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或get方法是才会创建它们。其实每个线程的本地变量不是存在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面。也就是说ThreadLocal类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面并存放起来。当调用线程调用它的get方法是,再从当前线程中的threadLocals变量里面将其拿出来使用。如果调用线程一直不终止,name这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。另外,Thread里面的threadLocals为何设计为map结构,是因为每个线程可以存储多个ThreadLocal变量。
1、set方法:
public void set(T value) { Thread t = Thread.currentThread();//获取当前线程 ThreadLocalMap map = getMap(t);//以当前线程为key,获取当前线程变量 if (map != null) map.set(this, value); else createMap(t, value);//初始化线程threadLocals } // ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue);//创建ThreadLocalMap对象(以当前ThreadLocal对象为key(firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)),firstValue为value)初始化;赋值给当前线程 }
2、get方法
public T get() { Thread t = Thread.currentThread();//获取当前线程 ThreadLocalMap map = getMap(t);//获取当前线程变量 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this);//Hashmap取值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1);//计算数组坐标 Entry e = table[i]; if (e != null && e.get() == key)//计算key值 return e; else return getEntryAfterMiss(key, i, e); }
3、remove方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } //ThreadLocalMap移除变量 private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
ThreadLocal不支持继承性,有关更多内容可以查看我的文章《Java线程变量问题-ThreadLocal》,地址:https://www.cnblogs.com/wangymd/p/11012658.html
三、ThreadLocal内存泄露问题
ThreadLocalMap使用ThreadLocal的弱引用未key,而value是强引用。如果ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。如果不做处理的话,value永远无法被GC回收,此时就可能会产生内存泄露。虽然ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录,但是建议使用完ThreadLocal方法后手动调用remove()方法。
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key)//查询到Entry return e; else return getEntryAfterMiss(key, i, e);//未查询到Entry } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null)//key为null情况 expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) {//key为null,删除线程变量值 e.value = null; tab[i] = null; size--; } else { 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; }