java集合-WeakHashMap(五)

版权声明:本文为博主原创文章,转载需注明出处. https://blog.csdn.net/piaoslowly/article/details/81909686

Date: 2017-01-14 10:10:12

java集合-WeakHashMap与ThreadLocal(五)

前面两篇文章《HashMap源码分析》,《Java基础-强,弱,软引用》已经把WeakHashMap用到的技术都讲完了。这篇文章也就没啥好讲的了。

WeakHashMap就是一个HashMap,只不过它的key继承了WeakReference表示key是一个弱引用,在GC时就会被回收。就这么简单。

WeakHashMap定义(转载)

  • WeakHashMap 继承于AbstractMap,实现了Map接口。
  • 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
  • 不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当系统发起GC后,key就会被回收了。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。

实现步骤:

  1. 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
    实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
  2. 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
  3. 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
    这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。

源码分析

Entry的定义

//Entry里面没看到K的定义哦,跑哪里去了?
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        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;
        }
}

key跑哪里去了?
看super(key,queue);
 |
 |  
 V
--------------- 
WeakReference.java里面哦。  
public WeakReference(T referent) {
        super(referent);
    }
看super(referent);    
---------------
 |
 |  
 V  
---------------
Reference.java里面哦。   
private T referent;    
Reference(T referent) {
        this(referent, null);
    }
-----------------    

解说:可以看到key直接被定义为弱引用了。
定义为弱引用之后,GC就可以随意回收它了。呵呵

expungeStaleEntries的定义

expungeStaleEntries监控queue队列,当key被回收时,queue里面会接受到GC发送过来的回收消息,expungeStaleEntries这个监控到queue不为空时,就把对应的value也置为空,这样value也就可以被回收了。

WeakHashMap和HashMap基本一样,所以不细看其他部分的源码,主要是这个expungeStaleEntries方法里面。

//WeakHashMap里面定义了一个成员变量queue,GC在回收key之后会发送通知,key被回收了。发送的通知会放到queue。
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

//这个方法是干什么的呢?
//我们上面说了key是弱引用,但是value是强引用啊,所以这个方法的作用是回收value。
//如果key被回收了,则将value=null,这样就可以让GC把value也回收了。
private void expungeStaleEntries() {
        //queue里面的数据时GC主动往里面添加的,所以在WeakHashMap代码里面我们是看不到queue的赋值的。
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);

                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                while (p != null) {
                    Entry<K,V> next = p.next;
                    if (p == e) {
                        if (prev == e)
                            table[i] = next;
                        else
                            prev.next = next;
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

在《java基础-强,软,弱引用》中我们讲了弱引用的相关信息,但是别和这里的混淆哦,所以这里在讲解一下。
private static WeakHashMap

实战分析一下GC回收

vm参数:-verbose:gc -Xms20M -Xms20M -Xmn10M -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintGCDetails -XX:SurvivorRatio=8
堆内存大小20M,年轻带堆内存10M。

在未执行到 byte[] a = new byte[1024 * 1024 * 2]这里的时候,堆内存还有足够空间,没有做回收操作,
当执行了 byte[] a = new byte[1024 * 1024 * 2]后
开始触发GC垃圾回收了,由于内存严重不够,所以连同触发了FULL GC。
回收后,对内存里面还剩下eden space 8192K, 27% used
什么问题,因为a引用的对象没有被回收,a是一个强引用。

图中为什我要在a前后都获取一次呢?为了测试value有没有被回收掉。
在WeakHashMap中,不管是get,还是put,还是size,几乎所有操作都会先去调用一下expungeStaleEntries这个方法,先把被GC回收掉的key从WeakHashMap中移除,同时把key所对应的value置为null帮助GC回收。

在图中,先添加了8M的对象在caches,并没有出发GC回收,当执行a对象时才触发GC操作,GC也只是回收key,而并不会主动回收value,那么问题来了,我并没有调用get,put,size操作啊,按道理是不会执行WeakHashMap里面的expungeStaleEntries这个方法的,因为只有操作了put,get等操作时才会进入expungeStaleEntries这个方法。但是打印出来的heap(堆)信息确告诉我,value也被回收了,那只能说明GC去调用了exp这个方法了。

上面图中,我故意使用两组做测试,第一组在GC后不做任何操作,第二组GC后,调用一下WeakHashMap个get操作,就是让他主动出发一下expungeStaleEntries,这样做就会让GC把value也回收了,但是两组对比后发现堆里面的大小是一样的,从而证明了GC回收key之后会调用一次exp这个方法。

为什么我要做这个对比呢?因为ThreadLocal这个类,它的操作和WeakHashMap是很相像的,但是ThreadLocal需要主动去清理value的。
key是弱引用,GC会帮我回收它,但是value确一直存在着,这就会导致内存泄漏了。

ThreadLocal

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。
另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

查看ThreadLocal源码:在Thread类中保有ThreadLocal.ThreadLocalMap的引用,即在一个Java线程栈中指向了堆内存中的一个ThreadLocal.ThreadLocalMap的对象,此对象中保存了若干个Entry,每个Entry的key(ThreadLocal实例)是弱引用,value是强引用(这点类似于WeakHashMap)。用到弱引用的只是key,每个key都弱引用指向threadLocal,当把threadLocal实例置为null以后,没有任何强引用指向threadLocal实例,所以threadLocal将会被gc回收,但是value却不能被回收,因为其还存在于ThreadLocal.ThreadLocalMap的对象的Entry之中。只有当前Thread结束之后,所有与当前线程有关的资源才会被GC回收。所以,如果在线程池中使用ThreadLocal,由于线程会复用,而又没有显示的调用remove的话的确是会有可能发生内存泄露的问题。

其实在ThreadLocal.ThreadLocalMap的get或者set方法中会探测其中的key是否被回收(调用expungeStaleEntry方法),然后将其value设置为null,这个功能几乎和WeakHashMap中的expungeStaleEntries()方法一样。因此value在key被gc后可能还会存活一段时间,但最终也会被回收,但是若不再调用get或者set方法时,那么这个value就在线程存活期间无法被释放。

ThreadLocal的key也是弱引用,为什么它的value会导致内存泄漏呢?

来看两组数据
设置堆栈大小:-verbose:gc -Xmx20M -Xms20M -Xmn10M -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintGCDetails -XX:SurvivorRatio=8

对照组WeakHashMap

public class ThreadLockDome {
    private static class ThreadMap implements Runnable {
        private WeakHashMap<String, byte[]> a = new WeakHashMap<String, byte[]>();
        public void run() {
            try {
                a.put(Thread.currentThread().getName(),new byte[1024 * 1024 * 3]);
                TimeUnit.SECONDS.sleep(3);
                if(a.get(Thread.currentThread().getName())!=null){
                    System.out.println("a==11111");
                }else{
                    System.out.println("a=====null null");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            ThreadMap map = new ThreadMap();
            Thread t = new Thread(map);
            t.start();
        }
        Thread.sleep(5000);
        byte[] c = new byte[1024 * 1024 * 2];
        System.out.println("---c---");
    }
}
输出日志:
[GC [PSYoungGen: 7799K->432K(9216K)] 7799K->6576K(19456K), 0.0071480 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 
[Full GC [PSYoungGen: 432K->0K(9216K)] [ParOldGen: 6144K->6446K(10240K)] 6576K->6446K(19456K) [PSPermGen: 2848K->2847K(21504K)], 0.0135840 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 6774K->3073K(9216K)] [ParOldGen: 6446K->9508K(10240K)] 13220K->12581K(19456K) [PSPermGen: 2855K->2855K(21504K)], 0.0080190 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
a==11111
a=====null null
a==11111
a==11111
a=====null null
[Full GC [PSYoungGen: 6554K->0K(9216K)] [ParOldGen: 9508K->303K(10240K)] 16062K->303K(19456K) [PSPermGen: 3039K->3039K(21504K)], 0.0098200 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
---a---
Heap
 PSYoungGen      total 9216K, used 2148K [0x00000007ff600000, 0x0000000800000000, 0x0000000800000000)
  eden space 8192K, 26% used [0x00000007ff600000,0x00000007ff8192c8,0x00000007ffe00000)
  from space 1024K, 0% used [0x00000007ffe00000,0x00000007ffe00000,0x00000007fff00000)
  to   space 1024K, 0% used [0x00000007fff00000,0x00000007fff00000,0x0000000800000000)
 ParOldGen       total 10240K, used 303K [0x00000007fec00000, 0x00000007ff600000, 0x00000007ff600000)
  object space 10240K, 2% used [0x00000007fec00000,0x00000007fec4bf28,0x00000007ff600000)
 PSPermGen       total 21504K, used 3046K [0x00000007f9a00000, 0x00000007faf00000, 0x00000007fec00000)
  object space 21504K, 14% used [0x00000007f9a00000,0x00000007f9cf9b58,0x00000007faf00000)

可以看到我开启了5个线程,每个线程里面存储了3M的数据。
a.put(Thread.currentThread().getName(),new byte[1024 * 1024 * 3]);
注意这里TimeUnit.SECONDS.sleep(3);
我让每个线程停留3秒种,如果不停留线程执行太快,线程执行完毕后就会立即释放内存,也就可能看不出3*5=15M内存的情况了。
当5个线程都执行到a.put的时候,它们接下来都需要等待3秒钟,也就是说占用的内存不会立即释放了,它们现在占用的内存为3*5=15M哦。
GC看到a是一个弱引用,发生GC时就会直接回收它,所以我们看到本来应该是5个a====1111,现在的结果是有两个a==null null。
说明被回收了。
接下来c又存入了2M的对象在堆里面,也没问题了,前面有部分堆内存被回收了,现在有足够的内存来容纳c了。

上面的日志不一定哦,因为是多线程,也有可能WeakHashMap里面还没回收呢就执行到了
byte[] c = new byte[1024 * 1024 * 2];就会导致c对象内存溢出,也就是主线程内存溢出。

把a里面put入4M的对象。
a.put(Thread.currentThread().getName(),new byte[1024 * 1024 * 4]);

结果:
[GC-- [PSYoungGen: 5915K->5915K(9216K)] 14107K->14107K(19456K), 0.0017590 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 5915K->4404K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 14107K->12596K(19456K) [PSPermGen: 2956K->2955K(21504K)], 0.0136470 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 
[GC-- [PSYoungGen: 4404K->4404K(9216K)] 12596K->12596K(19456K), 0.0015570 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 4404K->4404K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 12596K->12596K(19456K) [PSPermGen: 2955K->2955K(21504K)], 0.0070020 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GCException in thread "Thread-3" -- [PSYoungGen: 4652K->4652K(9216K)] 12844K->12844K(19456K), 0.0019940 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 4652K->4404K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 12844K->12596K(19456K) [PSPermGen: 2955K->2955K(21504K)], 0.0089340 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC-- [PSYoungGen: 4404K->4404K(9216K)] 12596K->12596K(19456K), 0.0018320 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 4404K->4404K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 12596K->12596K(19456K) [PSPermGen: 2955K->2955K(21504K)], 0.0090810 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Exception in thread "Thread-4" java.lang.OutOfMemoryError: Java heap space
    at cn.collection.ThreadLockDome$ThreadMap.run(ThreadLockDome.java:33)
    at java.lang.Thread.run(Thread.java:745)
java.lang.OutOfMemoryError: Java heap space
    at cn.collection.ThreadLockDome$ThreadMap.run(ThreadLockDome.java:33)
    at java.lang.Thread.run(Thread.java:745)
a=====null null
a=====null null
a=====null null
---c---
Heap
 PSYoungGen      total 9216K, used 7162K [0x00000007ff600000, 0x0000000800000000, 0x0000000800000000)
  eden space 8192K, 87% used [0x00000007ff600000,0x00000007ffcfe940,0x00000007ffe00000)
  from space 1024K, 0% used [0x00000007fff00000,0x00000007fff00000,0x0000000800000000)
  to   space 1024K, 0% used [0x00000007ffe00000,0x00000007ffe00000,0x00000007fff00000)
 ParOldGen       total 10240K, used 8192K [0x00000007fec00000, 0x00000007ff600000, 0x00000007ff600000)
  object space 10240K, 80% used [0x00000007fec00000,0x00000007ff400020,0x00000007ff600000)
 PSPermGen       total 21504K, used 3071K [0x00000007f9a00000, 0x00000007faf00000, 0x00000007fec00000)
  object space 21504K, 14% used [0x00000007f9a00000,0x00000007f9cffd80,0x00000007faf00000)

三个a被回收了,另外两个a在回收时直接抛出了堆栈溢出了。

实验组ThreadLocal:

public class ThreadLockDome {
    private static class ThreadMap implements Runnable {
        private ThreadLocal<byte[]> b = new ThreadLocal<byte[]>();

        public void run() {
            try {
                b.set(new byte[1024 * 1024 * 3]);
                TimeUnit.SECONDS.sleep(3);
                if (b.get() != null) {
                    System.out.println(Thread.currentThread().getName()+"=====111111");
                } else {
                    System.out.println(Thread.currentThread().getName()+"====null null null");
                } 
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadMap map = new ThreadMap();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(map);
            t.start();
        }
        Thread.sleep(5000);
        byte[] c = new byte[1024 * 1024 * 2];
        System.out.println("---c---");
    }
}
输出结果:
[GC [PSYoungGen: 7635K->416K(9216K)] 7635K->6560K(19456K), 0.0040800 secs] [Times: user=0.01 sys=0.01, real=0.01 secs] 
[Full GC [PSYoungGen: 416K->0K(9216K)] [ParOldGen: 6144K->6445K(10240K)] 6560K->6445K(19456K) [PSPermGen: 2849K->2848K(21504K)], 0.0132550 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 6624K->3073K(9216K)] [ParOldGen: 6445K->9507K(10240K)] 13069K->12580K(19456K) [PSPermGen: 2855K->2855K(21504K)], 0.0067770 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Thread-2=====111111
Thread-4=====111111
Thread-3=====111111
Thread-1=====111111
Thread-0=====111111
[Full GC [PSYoungGen: 6612K->0K(9216K)] [ParOldGen: 9507K->303K(10240K)] 16120K->303K(19456K) [PSPermGen: 3040K->3040K(21504K)], 0.0070590 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
---c---
Heap
 PSYoungGen      total 9216K, used 2162K [0x00000007ff600000, 0x0000000800000000, 0x0000000800000000)
  eden space 8192K, 26% used [0x00000007ff600000,0x00000007ff81c9f8,0x00000007ffe00000)
  from space 1024K, 0% used [0x00000007ffe00000,0x00000007ffe00000,0x00000007fff00000)
  to   space 1024K, 0% used [0x00000007fff00000,0x00000007fff00000,0x0000000800000000)
 ParOldGen       total 10240K, used 303K [0x00000007fec00000, 0x00000007ff600000, 0x00000007ff600000)
  object space 10240K, 2% used [0x00000007fec00000,0x00000007fec4bfa0,0x00000007ff600000)
 PSPermGen       total 21504K, used 3047K [0x00000007f9a00000, 0x00000007faf00000, 0x00000007fec00000)
  object space 21504K, 14% used [0x00000007f9a00000,0x00000007f9cf9c38,0x00000007faf00000)

从日志可以看到虽然发生了GC和Full GC,但是ThreadLocal里面并没有被回收了,5个线程的值都不为null。

这句:[Full GC [PSYoungGen: 6612K->0K(9216K)]说明什么,当线程执行完毕后,内存就立马释放了。

改为4M后:

[GC-- [PSYoungGen: 5423K->5423K(9216K)] 13615K->13615K(19456K), 0.0039000 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 5423K->4395K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 13615K->12587K(19456K) [PSPermGen: 2811K->2810K(21504K)], 0.0163100 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 
[GC-- [PSYoungGen: 4395K->4395K(9216K)] 12587K->12587K(19456K), 0.0018300 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GCException in thread "Thread-3"  [PSYoungGen: 4395K->4395K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 12587K->12587K(19456K) [PSPermGen: 2810K->2810K(21504K)], 0.0077320 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC-- [PSYoungGen: 5056K->5056K(9216K)] 13248K->13248K(19456K), 0.0020430 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[Full GC [PSYoungGen: 5056K->4387K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 13248K->12579K(19456K) [PSPermGen: 2817K->2817K(21504K)], 0.0125060 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC-- [PSYoungGen: 4387K->4387K(9216K)] 12579K->12579K(19456K), 0.0014050 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 4387K->4387K(9216K)] [ParOldGen: 8192K->8192K(10240K)] 12579K->12579K(19456K) [PSPermGen: 2817K->2817K(21504K)], 0.0182230 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
Exception in thread "Thread-4" java.lang.OutOfMemoryError: Java heap space
    at cn.collection.ThreadLockDome$ThreadMap.run(ThreadLockDome.java:24)
    at java.lang.Thread.run(Thread.java:745)
java.lang.OutOfMemoryError: Java heap space
    at cn.collection.ThreadLockDome$ThreadMap.run(ThreadLockDome.java:24)
    at java.lang.Thread.run(Thread.java:745)
Thread-2=====111111
Thread-0=====111111
Thread-1=====111111
---c---
Heap
 PSYoungGen      total 9216K, used 7198K [0x00000007ff600000, 0x0000000800000000, 0x0000000800000000)
  eden space 8192K, 87% used [0x00000007ff600000,0x00000007ffd07928,0x00000007ffe00000)
  from space 1024K, 0% used [0x00000007fff00000,0x00000007fff00000,0x0000000800000000)
  to   space 1024K, 0% used [0x00000007ffe00000,0x00000007ffe00000,0x00000007fff00000)
 ParOldGen       total 10240K, used 8192K [0x00000007fec00000, 0x00000007ff600000, 0x00000007ff600000)
  object space 10240K, 80% used [0x00000007fec00000,0x00000007ff400020,0x00000007ff600000)
 PSPermGen       total 21504K, used 3071K [0x00000007f9a00000, 0x00000007faf00000, 0x00000007fec00000)
  object space 21504K, 14% used [0x00000007f9a00000,0x00000007f9cffe78,0x00000007faf00000)

可以看到前3个线程
Thread-2=====111111
Thread-0=====111111
Thread-1=====111111
没有被GC回收,导致后了后2个进来后就直接抛堆空间异常了。
这行日志“—c—”之所以能打印出来,就是因为5个线程执行结束后内存直接回收了,所以这样c在创建2M大小的对象时也是没有问题的。

总结:

可以看到WeakHashMap的key是弱引用,ThreadLocal的key也是用的弱引用,但是WeakHashMap在被GC回收时value也会被回收了,而ThreadLocal则不会,ThreadLocal必须显示的调用一下remove方法才能将value的值给清空。
在两个的源码中可以看到,ThreadLocal里面并没有一个接受对象被GC回收后通知的ReferenceQueue,所以就算key被回收了,value也是存在的,并不会和WeakHashMap一样,在key被清空后,可以使用ReferenceQueue这个队列接受被GC的通知,然后把value也自己给清空。

ThreadLocal源码分析

讲完了上面的再来讲源码终于明白了ThreadLocal的原理和他是用来干嘛的了,之前一直半懂半不懂的。

set方法

public void set(T value) {
        //看到了吗?t是个当前线程,不懂Thread.currentThread这个的可以去看看,不然真不能理解ThreadLocal
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

//获取当前线程的ThreadLocalMap,要知道“每个线程“里面都有一个ThreadLocalMap哦! 
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

//如果ThreadLocalMap这个为null则创建一个
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

//如果ThreadLocalMap这个已经存在了,则往里面添加值ThreadLocalMap就像一个WeakHashMap
private void set(ThreadLocal key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

//可以看到key为弱引用哦,可以参看WeakHashMap观看哦!
static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }        

TheadLocal就像一个SetHashMap,
ThreadLocalMap就像一个WeakHashMap。

SetHashMap是利用HashMap的key来做去重的,HashMap的key虽然可以重复,但是后进来的会覆盖先进来的,
put(1,”2”);
put(1,”3”);
最后存入的是put(1,”3”);
put(1,”2”)会被覆盖,
这样SetHashMap就利用key来做去重。set(1),set(1)最后遍历的时候只会有一个set(1).

ThreadLocal存数据也是set(1).他和setHashMap正好反着来。使用value做value,那么key呢?key使用的是this,表示当前线程的ThreadLocal。

接着最上面的实例分析:


private static class ThreadMap implements Runnable {
         private WeakHashMap<String, byte[]> a = new WeakHashMap<String, byte[]>();
        private ThreadLocal<byte[]> b = new ThreadLocal<byte[]>();

        public void run() {
            try {
                b.set(new byte[1024 * 1024 * 1]);
                a.put("1",new byte[1024 * 1024 * 1]);
                TimeUnit.SECONDS.sleep(3);
                if (b.get() != null) {
                    System.out.println(Thread.currentThread().getName()+"=====111111");
                } else {
                    System.out.println(Thread.currentThread().getName()+"====null null null");
                } 
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
}


public static void main(String[] args) throws InterruptedException {
        ThreadMap map = new ThreadMap();
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(map);
            t.start();
        }
        Thread.sleep(5000);
        byte[] c = new byte[1024 * 1024 * 2];
        System.out.println("---c---");
    }

分析:
先来看ThreadMap这个类里面有两个成员变量,a,b,他们有什么区别呢?
b.set(new byte[1024 * 1024 * 1]);
a.put(“1”,new byte[1024 * 1024 * 1]);

先是5个线程拥有同一个对象map。

为什么b是一个公共变量,存入的数据却是每个线程独立拥有的呢?
就看源码了呗!

public void set(T value) {
        //看到了吗?t是个当前线程,不懂Thread.currentThread这个的可以去看看,不然真不能理解ThreadLocal
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Thread.currentThread();看到这个了嘛,这个表示当前线程,只和线程有关。
Thread t = new Thread(map);这里new了5个线程,那么就有5个Thread.currentThread();

来,我们尝试一下改变WeakHashMap变的和ThreadLocal差不多的功能。
a.put(Thread.currentThread().getName(),new byte[1024 * 1024 * 1]);
这样就OK了吗,每个线程进来用当前线程的名字做为key。
获取的时候使用a.get(Thread.currentThread().getName())就OK了啊。这样是不是更能理解ThreadLocal了。

参考地址

http://www.cnblogs.com/skywang12345/p/3311092.html

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81909686