ThreadLocal
前几天看了@华为kim的threadlocal的博文深有感触,所有在这再次总结一下我对threadlocal的源码理解,以及内部机制。
数据结构
下面看一下threadlocal的数据结构:每一个Thread内部都有一个 ThreadLocal.ThreadLocalMap threadLocals 对象 , 而ThreadLocalMap 中,维护着一个Entry[]数组,每个Entry对象,包含一个弱引用的ThreadLocal和一个value。
内部机制
SET操作
public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //根据当前线程,找到线程内部的ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) //赋值 当前的ThreadLocal 和 value map.set(this, value); else //创建线程内部的ThreadLocalMap createMap(t, value); }
GET操作
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { //根据当前ThreadLocal为key,获取对应的Entry,并获取value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //返回默认值 return setInitialValue(); }
原理分析
1)为什么ThreadLocalMap要用WeakReference来封装key?
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
WeakReference有以下特性:
当一个对象A只有WeakReference引用指向它是,那么A在下一次gc的时候就会被回收掉。想象下,如果ThreadLocalMap中某个key已经不用了,最终只会有一个WeakReference指向它,
这个key自然就可以被回收掉,不会一直停留在ThreadLocalMap中。(对引用将在后面详细介绍)
如果ThreadLocal被回收掉了,那么value怎么回收?
在ThreadLocalMap的get和set方法中,根据ThreadLocal经过hash,获取到的Entry,如果Entry的key=null,执行expungeStaleEntry(i)方法,该方法的主要操作把哈希表当前位置的无用数据清理掉(当然还有别的操作)。
总之,假设某个threadlocal对象无效,这个对象本身会在下次gc被回收,对应的value值也会在某次ThreadLocal调用中被释放;如果某个thread死掉了,它对应的threadlocal内容自动释放。
2)为什么要用开放地址实现Hash冲突呢?
ThreadLocalMap的 Entry[] table 表不同于HashMap的链表法解决冲突。
a. 节省内存空间,链表使用的空间大于数组;
b. threadLocalMap设计的哈希key可以尽可能避免哈希冲突;
c. 清理数据效率高,毕竟遍历数组比遍历链表效率高;
java中的引用
在深入理解JVM一书中,谈及了强引用,软引用,弱引用,虚引用。但笔者没有当时深入研究,下面是对java中引用的详细总结。
WeakHashMap
在介绍引用之前,先来看一下WeakHashMap的实现原理,先给出下面的测试用例,
public class test { public static void main(String[] args) { Map<Integer, Object> map = new HashMap<>(); for (int i = 0; i < 10000; i++) { Integer ii = new Integer(i); map.put(ii, new byte[i]); } } } //内存溢出 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.test.test.main(test.java:18) //将HashMap改成WeakHashMap 无任何报错
实际上,WeakHashMap指定了弱键,当只有一个弱引用指向该key,那么在内存不足,下次GC的时候清除该域。此外,在WeakHashMap的set(),get(),size()操作中,都间接或者直接调用了expungeStaleEntries()方法,以清理持有弱引用的key的表项。
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; final int hash; Entry<K,V> next; /** * Creates new entry. */ Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) { super(key, queue); this.value = value; this.hash = hash; this.next = next; }
软引用与弱引用
引用的使用十分广泛,对强引用和虚引用就不一一介绍了,下面重点介绍两个引用,软引用和弱引用。
看下面示例:
public class test { public static void main(String[] args) { /*Test1*/ Pojo pojo = new Pojo("Test1"); WeakReference<Pojo> sr = new WeakReference<Pojo>(pojo); //如果添加下面的软应引用,而软引用只有在内存不足的情况下才会删除对象,所有会打印 Test1 //SoftReference<Pojo> sf = new SoftReference<Pojo>(pojo); pojo = null; //此时只有一个弱引用指向对象 System.gc(); System.out.println(sr.get()); //null } static class Pojo { String value; Pojo(String value) { this.value = value; } public String toString() { return this.value; } } }
总结
WeakReference:每次gc的时候,如果一个对象A只被WeakReference直接引用,那么A就可以被回收掉;
SoftReference:每当内存不足的时候(其实和WeakReference差不多),如果一个对象A只被SoftReference或WeakReference直接引用,那么A就可以被回收掉;
注意:内存不足或者gc的时候,回收的不是reference对象本身,而是reference所引用的对象。