点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人。
文章不定期同步公众号,还有各种一线大厂面试原题、我的学习系列笔记。
public class ThreadLocal<T> { ... static class ThreadLocalMap { //该table类似于hashmap一样,拿来装value值,每个线程都有一个自己的ThreadLocalMap类,这些value值是每个线程通过共用一个ThreadLocal各自保存到自己的ThreadLocalMap中的table数组里 private Entry[] table; //可知ThreadLocalMap的内部类Entry继承WeakReference,所以Entry是弱引用,进而引发使用ThreadLocal类时的“内存溢出”问题 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //该方法可以看出ThreadLocal.ThreadLocalMap保存元素时,key为“公用ThreadLocal变量”,value是真正想保存的值 private void set(ThreadLocal<?> key, Object value) { ... } } }
每个线程都执行"公用ThreadLocal变量"的set(value)保存值->
"公用ThreadLocal变量"的set(value)里先currentThread()得到当前线程->
得到当前线程的【ThreadLocal.ThreadLocalMap threadLocals】->
往threadLocals里面存值:
threadLocals.set(公用ThreadLocal变量,value)->
threadLocals的set方法先用“公用ThreadLocal变量”计算table数组的下标,再把value和“公用ThreadLocal变量”都封装在Entry对象里面做属性,最后再把该封装的【Entry{value}】对象保存到table数组该下标处->
如果table数组该下标处已有元素,即冲突了,采用线性探测法不断计算下一个下标
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } //线性探测法:ThreadLocal.ThreadLocalMap的属性“private Entry[] table”保存元素发生冲突时采用线性探测法,len为table初始长度16: private static int nextIndex(int i, int len) { //可知线性探测每次+1 return ((i + 1 < len) ? i + 1 : 0); }
每个线程都执行"公用ThreadLocal变量"的get()方法获取value值->
"公用ThreadLocal变量"的get()方法里先currentThread()得到当前线程->
得到当前线程的【ThreadLocal.ThreadLocalMap threadLocals】->
threadLocals.get(公用ThreadLocal变量)->
threadLocals的get方法里先用“公用ThreadLocal变量”计算table数组的下标,然后直接取该下标处的【Entry{value}】对象->
得到Entry对象后再取出它的value属性就是想要取的值
public T get() { //直接就得到了当前线程 Thread t = Thread.currentThread(); //获取每个线程自有的ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { //传入this="公用ThreadLocal"去计算ThreadLocalMap的table下标,拿到下标后返回该下标位置的【Entry{value}】对象 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { //直接取【Entry{value}】对象的属性value就是想要的值 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } //map.getEntry(this):传入this="公用ThreadLocal"去计算ThreadLocalMap的table下标,拿到下标后返回该下标位置的【Entry{value}】对象 private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
使用场景:数据库连接、session管理、父子线程共享线程变量(如token)
如下使用ThreadLocal为每个线程创建数据库连接
import java.sql.Connection; import java.sq1.Statement; /** 主线程中定义static型的ThreadLocal,,每个线程第一次获取connection时, 都会往自己的属性threadLocals 中存一份自己的connection,往后直接用就可以了 */ public class TopicDao { //使用ThreadLocal保存Connection变量 private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); public static Connection getConnection(){ //如果connThreadLocal没有本线程对应的Connection则创建一个新的Connection,并将其作为value保存到本线程的ThreadLocalMap中 if (connThreadLocal.get() == nu1l) { Connection conn = ConnectionManager.getConnection(); connThreadLocal.set(conn); return conn; }else{ //直接返回当前线程的value return connThreadLocal.get(); } public void addTopic() { //从ThreadLocal中获取当前线程对应的数据库连接 Statement stat=getConnection().createStatement(); }
内存泄漏、造成脏数据
(1)内存泄漏
ThreadLocalMap也是一个map,它的key为"公用ThreadLocal变量",value为想要保存的值,value会被保存在ThreadLocalMap的一个数组属性【private Entry[] table】中,而Entry继承WeakReference是弱引用,弱引用的对象在下次GC时会被回收。假如table[0]已经保存了某个Entry对象,由上面“用ThreadLocal存东西”可知Entry对象中有两个属性【value和“公用ThreadLocal变量”】,它们都为强引用,当该table下标位置被重新赋值为另外一个Entry对象时,原Entry对象就失去外部的强引用指针,下次gc会回收该Entry对象,但是该Entry对象里面的两个强引用属性【value和“公用ThreadLocal变量”】则不会被回收,导致内存泄漏,解决:每个线程使用完ThreadLocal之后,显式调用ThreadLocal的remove(),它会帮忙释放两个强引用属性的内存(直接置为null)
public void remove() { //获取当前线程 ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) //this=公用的ThreadLocal变量 m.remove(this); } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; //利用"公用的ThreadLocal变量"计算下标 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //移除两个强引用属性中的【“公用ThreadLocal变量”】 e.clear(); //移除两个强引用属性中的【value】 expungeStaleEntry(i); return; } } }
(2)脏数据:线程池会复用Thread对象,故而Thread对象中的static静态属性ThreadLocal也会被复用,所以需要显示调用ThreadLocal的remove()清理掉上一个线程的信息,否则若下一个复用的线程若不调用set()设置该线程初始值,而直接调用get()就会获取到它前一个线程设置的值
OK,如果文章哪里有错误或不足,欢迎各位留言。
创作不易,各位的「三连」是二少创作的最大动力!我们下期见!