Java引用详解(强引用、软引用、弱引用、虚引用)

来源:互联网 发布:2016淘宝女装排行榜 编辑:程序博客网 时间:2024/05/19 23:01

 Java引用详解(强引用、软引用、弱引用、虚引用)

ThreadLocal

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。可以理解为如下三点:
1、每个线程都有自己的局部变量
每个线程都有一个独立于其他线程的上下文来保存这个变量,一个线程的本地变量对其他线程是不可见的。
2、独立于变量的初始化副本
ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝。
3、状态与某一个线程相关联
ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的私有状态而引入的一个机制,理解这点对正确使用ThreadLocal至关重要。

ThreadLocal的接口方法

[java] view plain copy
  1. public T get() { }    
  2. public void set(T value) { }    
  3. public void remove() { }    
  4. protected T initialValue() { }    

get()用来获取当前线程中变量的副本(保存在ThreadLocal中)。

set()用来设置当前线程中变量的副本。

remove()用来移除当前线程中变量的副本。

initialValue()是一个protected方法,用来给ThreadLocal变量提供初始值,每个线程都会获取这个初始值的一个副本。

使用示例

[java] view plain copy
  1. public class ThreadLocalTest {    
  2.         //创建一个Integer型的线程本地变量    
  3.         public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {    
  4.             @Override    
  5.             protected Integer initialValue() {    
  6.                 return 0;    
  7.             }    
  8.         };    
  9.         //计数    
  10.         static class Counter implements Runnable{    
  11.             @Override    
  12.             public void run() {    
  13.                 //获取当前线程的本地变量,然后累加100次    
  14.                 int num = local.get();    
  15.                 for (int i = 0; i < 100; i++) {    
  16.                     num++;    
  17.                 }    
  18.                 //重新设置累加后的本地变量    
  19.                 local.set(num);    
  20.                 System.out.println(Thread.currentThread().getName() + " : "+ local.get());    
  21.             }    
  22.         }    
  23.         public static void main(String[] args) throws InterruptedException {    
  24.             Thread[] threads = new Thread[5];    
  25.             for (int i = 0; i < 5; i++) {            
  26.                 threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]");    
  27.                 threads[i].start();    
  28.             }     
  29.         }    
  30.     }    
输出:

CounterThread-[2] : 100
CounterThread-[0] : 100
CounterThread-[3] : 100
CounterThread-[1] : 100
CounterThread-[4] : 100

对initialValue函数的正确理解

[java] view plain copy
  1. public class ThreadLocalMisunderstand {    
  2.     
  3.     static class Index {    
  4.         private int num;     
  5.         public void increase() {    
  6.             num++;    
  7.         }    
  8.         public int getValue() {    
  9.             return num;    
  10.         }    
  11.     }    
  12.     private static Index num=new Index();    
  13.     //创建一个Index型的线程本地变量    
  14.     public static final ThreadLocal<Index> local = new ThreadLocal<Index>() {    
  15.         @Override    
  16.         protected Index initialValue() {    
  17.             return num;    
  18.         }    
  19.     };    
  20.     //计数    
  21.     static class Counter implements Runnable{    
  22.         @Override    
  23.         public void run() {    
  24.             //获取当前线程的本地变量,然后累加10000次    
  25.             Index num = local.get();    
  26.             for (int i = 0; i < 10000; i++) {    
  27.                 num.increase();    
  28.             }    
  29.             //重新设置累加后的本地变量    
  30.             local.set(num);    
  31.             System.out.println(Thread.currentThread().getName() + " : "+ local.get().getValue());    
  32.         }    
  33.     }    
  34.     public static void main(String[] args) throws InterruptedException {    
  35.         Thread[] threads = new Thread[5];    
  36.         for (int i = 0; i < 5; i++) {            
  37.             threads[i] = new Thread(new Counter() ,"CounterThread-[" + i+"]");    
  38.         }     
  39.         for (int i = 0; i < 5; i++) {        
  40.             threads[i].start();    
  41.         }    
  42.     }    
  43. }  
输出:

CounterThread-[0] : 12019
CounterThread-[2] : 14548
CounterThread-[1] : 13271
CounterThread-[3] : 34069
CounterThread-[4] : 34069

现在得到的计数不一样了,并且每次运行的结果也不一样,说好的线程本地变量呢?

之前提到,我们通过覆盖initialValue函数来给我们的ThreadLocal提供初始值,每个线程都会获取这个初始值的一个副本。而现在我们的初始值是一个定义好的一个对象,num是这个对象的引用。换句话说我们的初始值是一个引用。引用的副本和引用指向的不就是同一个对象吗?

如果我们想给每一个线程都保存一个Index对象应该怎么办呢?那就是创建对象的副本而不是对象引用的副本。

[java] view plain copy
  1. private static ThreadLocal<Index> local = new ThreadLocal<Index>() {    
  2.     @Override    
  3.     protected Index initialValue() {    
  4.         return new Index(); //注意这里,新建一个对象    
  5.     }    
  6. };    

ThreadLocal源码分析

存储结构


[java] view plain copy
  1. public class Thread implements Runnable {  
  2. ......  
  3.     ThreadLocal.ThreadLocalMap threadLocals = null;//一个线程对应一个ThreadLocalMap  
  4. ......  
  5. }  
[java] view plain copy
  1. public class ThreadLocal<T> {  
  2. ......  
  3.     static class ThreadLocalMap {//静态内部类  
  4.         static class Entry extends WeakReference<ThreadLocal> {//键值对  
  5.             //Entry是ThreadLocal对象的弱引用,this作为键(key)  
  6.             /** The value associated with this ThreadLocal. */  
  7.             Object value;//ThreadLocal关联的对象,作为值(value),也就是所谓的线程本地变量  
  8.   
  9.             Entry(ThreadLocal k, Object v) {  
  10.                 super(k);  
  11.                 value = v;  
  12.             }  
  13.         }  
  14.         ......  
  15.         private Entry[] table;//用数组保存所有Entry,采用线性探测避免冲突  
  16.     }  
  17. ......  
  18. }  

ThreadLocal源码

[java] view plain copy
  1. public class ThreadLocal<T> {  
  2.     /**ThreadLocals rely on per-thread linear-probe hash maps attached to each thread  
  3.        (Thread.threadLocals and inheritableThreadLocals).   
  4.        The ThreadLocal objects act as keys, searched via threadLocalHashCode.  */  
  5.     //每个线程对应一个基于线性探测的哈希表(ThreadLocalMap类型),通过Thread类的threadLocals属性关联。  
  6.     //在该哈希表中,逻辑上key为ThreadLocal,实质上通过threadLocalHashCode属性作为哈希值查找。  
  7.     private final int threadLocalHashCode = nextHashCode();  
  8.   
  9.     /**  The next hash code to be given out. Updated atomically. Starts at zero.*/  
  10.     private static AtomicInteger nextHashCode = new AtomicInteger();  
  11.   
  12.     private static final int HASH_INCREMENT = 0x61c88647;  
  13.   
  14.     /**Returns the next hash code.*/  
  15.     private static int nextHashCode() {  
  16.         return nextHashCode.getAndAdd(HASH_INCREMENT);  
  17.     }  
  18.   
  19.     /**Returns the current thread's "initial value" for this thread-local variable.   
  20.        This method will be invoked the first time a thread accesses the variable with the {@link #get} method,  
  21.        unless the thread previously invoked the {@link #set} method, in which case the <tt>initialValue</tt>  
  22.        method will not be invoked for the thread.  Normally, this method is invoked at most once per thread, 
  23.        but it may be invoked again in case of subsequent invocations of {@link #remove} followed by {@link #get}. 
  24.  
  25.        <p>This implementation simply returns <tt>null</tt>; if the programmer desires thread-local variables  
  26.        to have an initial value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be subclassed,  
  27.        and this method overridden.  Typically, an anonymous inner class will be used. 
  28.      * @return the initial value for this thread-local 
  29.      */  
  30.     //初始化线程本地变量,注意上面讲到的关于该方法的正确理解  
  31.     protected T initialValue() {  
  32.         return null;  
  33.     }  
  34.   
  35.     /**  Creates a thread local variable.*/  
  36.     public ThreadLocal() {  
  37.     }  
  38.   
  39.     /**Returns the value in the current thread's copy of this thread-local variable.  
  40.        If the variable has no value for the current thread, it is first initialized to the value returned 
  41.        by an invocation of the {@link #initialValue} method. 
  42.      * @return the current thread's value of this thread-local 
  43.      */  
  44.     public T get() {  
  45.         Thread t = Thread.currentThread();//获取当前线程对象  
  46.         ThreadLocalMap map = getMap(t);//获取当前线程对象关联的ThreadLocalMap  
  47.         if (map != null) {  
  48.             ThreadLocalMap.Entry e = map.getEntry(this);//以this作为key,查找线程本地变量  
  49.             if (e != null)//如果该线程本地变量已经存在,返回即可  
  50.                 return (T)e.value;  
  51.         }  
  52.         return setInitialValue();//如果该线程本地变量不存在,设置初始值并返回  
  53.     }  
  54.   
  55.     /**Variant of set() to establish initialValue.  
  56.        Used instead of set() in case user has overridden the set() method.   
  57.      * @return the initial value 
  58.      */  
  59.     private T setInitialValue() {  
  60.         T value = initialValue();//获取线程本地变量的初始值  
  61.         Thread t = Thread.currentThread();  
  62.         ThreadLocalMap map = getMap(t);  
  63.         if (map != null)//如果当前线程关联的ThreadLocalMap已经存在,将线程本地变量插入哈希表  
  64.             map.set(this, value);  
  65.         else  
  66.             createMap(t, value);//否则,创建新的ThreadLocalMap并将<this,value>组成的键值对加入到ThreadLocalMap中  
  67.         return value;  
  68.     }  
  69.   
  70.     /**Sets the current thread's copy of this thread-local variableto the specified value.   
  71.        Most subclasses will have no need to override this method, relying solely on the {@link #initialValue} 
  72.      * method to set the values of thread-locals. 
  73.      * 
  74.      * @param value the value to be stored in the current thread's copy of this thread-local. 
  75.      */  
  76.     public void set(T value) {  
  77.         Thread t = Thread.currentThread();  
  78.         ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap  
  79.         if (map != null)  
  80.             map.set(this, value);  
  81.         else  
  82.             createMap(t, value);  
  83.     }  
  84.   
  85.     /**Removes the current thread's value for this thread-local variable.   
  86.        If this thread-local variable is subsequently {@linkplain #get read} by the current thread,  
  87.        its value will be reinitialized by invoking its {@link #initialValue} method, 
  88.        unless its value is {@linkplain #set set} by the current thread in the interim.   
  89.        This may result in multiple invocations of the <tt>initialValue</tt> method in the current thread. 
  90.      * 
  91.      * @since 1.5 
  92.      */  
  93.      public void remove() {//从当前线程的ThreadLocalMap中移除线程本地变量  
  94.          ThreadLocalMap m = getMap(Thread.currentThread());  
  95.          if (m != null)  
  96.              m.remove(this);  
  97.      }  
  98.   
  99.     /** Get the map associated with a ThreadLocal. Overridden in InheritableThreadLocal.*/  
  100.     ThreadLocalMap getMap(Thread t) {  
  101.         return t.threadLocals;  
  102.     }  
  103.   
  104.     /**Create the map associated with a ThreadLocal. Overridden in InheritableThreadLocal.*/  
  105.     //创建ThreadLocalMap后与当前线程关联,并将线程本地变量插入ThreadLocalMap  
  106.     void createMap(Thread t, T firstValue) {  
  107.         t.threadLocals = new ThreadLocalMap(this, firstValue);  
  108.     }  
  109.     //其他  
  110.     static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {  
  111.         return new ThreadLocalMap(parentMap);  
  112.     }  
  113.   
  114.     T childValue(T parentValue) {  
  115.         throw new UnsupportedOperationException();  
  116.     }  
  117.   
  118.     static class ThreadLocalMap {//基于线性探测解决冲突的哈希映射表  
  119.     ......  
  120.     }  
  121. ......  
  122. }  

内存泄露与WeakReference

[java] view plain copy
  1. static class Entry extends WeakReference<ThreadLocal> {  
  2.     /** The value associated with this ThreadLocal. */  
  3.     Object value;  
  4.   
  5.     Entry(ThreadLocal k, Object v) {  
  6.         super(k);  
  7.         value = v;  
  8.     }  
  9. }  
对于键值对Entry,key为ThreadLocal实例,value为线程本地变量。不难发现,Entry继承自WeakReference<ThreadLocal>。WeakReference就是所谓的弱引用,也就是说Key是一个弱引用(引用ThreadLocal实例)。
关于强引用、弱引用,参看:http://blog.csdn.net/sunxianghuang/article/details/52267282

[java] view plain copy
  1. public class Test {  
  2.     public static class MyThreadLocal extends ThreadLocal {  
  3.         private byte[] a = new byte[1024*1024*1];  
  4.           
  5.         @Override  
  6.         public void finalize() {  
  7.             System.out.println("My threadlocal 1 MB finalized.");  
  8.         }  
  9.     }  
  10.       
  11.     public static class My50MB {//占用内存的大对象  
  12.         private byte[] a = new byte[1024*1024*50];  
  13.           
  14.         @Override  
  15.         public void finalize() {  
  16.             System.out.println("My 50 MB finalized.");  
  17.         }  
  18.     }  
  19.     public static void main(String[] args) throws InterruptedException {  
  20.         new Thread(new Runnable() {  
  21.             @Override  
  22.             public void run() {  
  23.                 ThreadLocal tl = new MyThreadLocal();  
  24.                 tl.set(new My50MB());  
  25.                   
  26.                 tl=null;//断开ThreadLocal的强引用  
  27.                 System.out.println("Full GC 1");  
  28.                 System.gc();  
  29.                   
  30.             }  
  31.               
  32.         }).start();  
  33.         System.out.println("Full GC 2");  
  34.         System.gc();  
  35.         Thread.sleep(1000);  
  36.         System.out.println("Full GC 3");  
  37.         System.gc();  
  38.         Thread.sleep(1000);  
  39.         System.out.println("Full GC 4");  
  40.         System.gc();  
  41.         Thread.sleep(1000);  
  42.   
  43.     }  
  44. }  
输出:

Full GC 2
Full GC 1
My threadlocal 1 MB finalized.
Full GC 3
My 50 MB finalized.
Full GC 4

从输出可以看出,一旦threadLocal的强引用断开,key的内存就可以得到释放。只有当线程结束后,value的内存才释放。

每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap。Map中的key为一个threadlocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key。每个key都弱引用指向threadlocal。当把threadlocal实例置为null以后,没有任何强引用指threadlocal实例,所以threadlocal将会被gc回收。但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用。

只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.

注: 实线代表强引用,虚线代表弱引用.


所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露。但是value在threadLocal设为null线程结束这段时间不会被回收,就发生了我们认为的“内存泄露”。

因此,最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的,就可能出现内存泄露。  
为了最小化内存泄露的可能性和影响,在ThreadLocal的get,set的时候,遇到key为null的entry就会清除对应的value。

所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,或者get,set方法调用时依然没有遇到key为null的entry,那么这个期间就会发生真正的内存泄露。

使用ThreadLocal需要注意,每次执行完毕后,要使用remove()方法来清空对象,否则 ThreadLocal 存放大对象后,可能会OMM。

为什么使用弱引用

To help deal with very large and long-lived usages, the hash table entries use  WeakReferences for keys.

应用实例

Hibernate中使用Threadlocal实现线程相关的Session

参考:

JDK 1.7源码

http://my.oschina.NET/xpbug/blog/113444


阅读全文
0 0
原创粉丝点击