首页 > 技术文章 > java中的引用与ThreadLocal

gaojy 2017-09-14 18:15 原文

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所引用的对象。



 

推荐阅读