java中的引用与ThreadLocal

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

时间: 2024-11-20 21:17:38

java中的引用与ThreadLocal的相关文章

Java中线程封闭之ThreadLocal

在访问共享数据时通常使用同步.若不使用同步则可以将对象封闭在一个线程中达到线程安全的目的,该方法称为线程封闭(Thread Confinement).其中实现线程封闭中规范的方法是使用ThreadLocal类.线程封闭技术一种常用的使用场景是在JDBC Connection对象. public class ConnectionHelper{private final static String URL = "";private final static ThreadLocal<C

Java中没有引用传递只有值传递(在函数中)

◆传参的问题 引用类型(在函数调用中)的传参问题,是一个相当扯的问题.有些书上说是传值,有些书上说是传引用.搞得Java程序员都快成神经分裂了.所以,我们最后来谈一下“引用类型参数传递”的问题. 如下例子,假设现在要把刚才创建的那一坨字符串打印出来,我们会使用如下语句: StringBuffer str = new StringBuffer(); System.out.println(str); //这个语句又是什么意思捏?这时候就两说了. 第一种理解:可以认为传进函数的是str这个指针,指针说

Java中强引用、软引用、弱引用

Java 中强引用, 软引用SoftReference,弱引用WeakReference,虚引用 Java当中的引用有四种: 1.强引用  平常我们用的最多的引用. 强引用是使用最普遍的引用.如果一个对象具有强引用,那垃圾回收器绝不会回收它.当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题.只有当分配的内存对象不再有任何引用时,GC才可能开始回收其内存. <span style="font-si

请注意,java中没有引用传递-----转载

1 说明:本文的适用对象为java初学者.如果有读者发现文章中有叙述不妥之处,请指正. 2 3 今天在论坛上有人提了一个关于java中调用函数时有没有引用传递的问题,可谓是吵的不可开交.有人说java只有值传递,也有人说java既有值传递也有引用传递,那么java中到底有没有引用传递呢,下面我来分析一下. 4 5 一.首先来明确一下"值传递"和"引用传递的"区别 6 7 值传递:是对所传递参数进行一次副本拷贝,对参数的修改只是对副本的修改,函数调用结束,副本丢弃,原

Java中的引用和C++中引用的区别

首先了解C++ 中引用的含义:"引用"即"别名".C++中的引用代表的就是实际的存储空间.对其进行操作就是对存储空间进行操作. 而在Java中的引用:可以看做是C语言中的"指针"或者"地址".对java中引用的属性(即指针指向的存储空间)进行操作才是有效的. 1)Java引用作为函数(方法)参数 Java的方法参数只是传值,引用作为参数使用时,会给函数内引用的值的COPY,所以在函数内交换两个引用参数是没有意义的,因为函数交换

请注意:java中没有引用传递

说明:本文的适用对象为java初学者.如果有读者发现文章中有叙述不妥之处,请指正. 今天在论坛上有人提了一个关于java中调用函数时有没有引用传递的问题,可谓是吵的不可开交.有人说java只有值传递,也有人说java既有值传递也有引用传递,那么java中到底有没有引用传递呢,下面我来分析一下. 一.首先来明确一下"值传递"和"引用传递的"区别 值传递:是对所传递参数进行一次副本拷贝,对参数的修改只是对副本的修改,函数调用结束,副本丢弃,原来的变量不变(即实参不变)引

关于java中是引用传递还是值传递的问题!!!经常在笔试中遇到,今天终于弄明白了!

关于JAVA中参数传递问题有两种,一种是按值传递(如果是基本类型),另一种是按引用传递(如果是對象).首先以两个例子开始:1)public class Test2 { public static void main (String [] args) { StringBuffer a = new StringBuffer ("A"); StringBuffer b = new StringBuffer ("B"); operate (a,b); System.out.

java中虚引用PhantomReference与弱引用WeakReference(软引用SoftReference)的差别

之前的这篇博客介绍了java中4种引用的差别和使用场景,在最后的总结中提到: "软引用和弱引用差别不大,JVM都是先把SoftReference和WeakReference中的referent字段值设置成null,之后加入到引用队列:而虚引用则不同,如果某个堆中的对象,只有虚引用,那么JVM会将PhantomReference加入到引用队列中,JVM不会自动将referent字段值设置成null".这段总结写的比较仓促,也没有给出实际的例子加以佐证.本文主要是重申下这几种引用的差别,并

Java中的引用和指针

java中内存的分配方式有两种,一种是在堆中分配,一种是在堆栈中分配,所有new出来的对象都是在堆中分配的,函数中参数的传递是在栈中分配的.通常情况下堆的内存可以很大,比如32位操作系统中的虚拟内存都可以被堆所使用(当内存紧张的时候甚至硬盘都可以是堆的存储空间),而堆栈的内存分配是有限的. 这和c++中内存分配差不多.java中有几种基本类型如int,float,double,char,byte等,他们不是对象,除此之外一切都是对象,所有的对象都是在堆上分配的.但好像在C#中这些都有封装好的一些