Java 集合:(三十) WeakHashMap实现类

一、WeakHashMap 概述

  1、WeakHashMap的功能实现上面和HashMap等非常的相似,无非也是用来hash表+单向链表的结构作为底层数据存储,

  2、WeakHashMap的特点是以一种弱引用的关系存储数据,存储对象长期不用,可以被垃圾回收。

  3、

二、WeakHashMap 类结构

  1、WeakHashMap 类继承结构

    

  2、WeakHashMap 类签名

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {}

  

  3、WeakHashMap 方法列表

    

三、与 HashMap 异同

    WeakHashMap的实现和HashMap非常类似,他们有相同的数据结构,类似的存储机制。数据底层都是通过hash表+单向链表的结构存储数据。

  1、get

    获取元素的流程非常类似,首先对key进行hash处理,通过hash值寻找到对应的元素位置。数组元素保存的是单向链表,这是hash冲突导致的结果。然后对每个entry对比key值是否相同,最终找到对应的元素。

  2、put

    添加元素的流程也和HashMap类似,首先对key值hash处理,通过hash值求出对应的元素位置,然后通过对比单向链表中的key值,将元素添加到链表头部。最后会更加当前元素的数量与threshold对比,进行动态扩容。扩容部分的原理也和HashMap类似。

  3、getTable

    getTable方法是WeakHashMap特有的,这个方法是干什么用的呢?因为我们知道WeakHashMap的元素是通过弱引用的关系存储的。在容器中,有部分元素长时间未用,会被垃圾回收,getTable的作用就是清除被垃圾回收的元素。源码如下:

 1     privateEntry<K,V>[] getTable(){
 2         expungeStaleEntries();
 3         return table;
 4     }
 5 
 6     // 清除所有被垃圾回收的元素
 7     privatevoid expungeStaleEntries(){
 8     // queue队列中保存的是已经被垃圾回收的元素key值。遍历队列
 9         for(Object x;(x = queue.poll())!=null;){
10             synchronized(queue){
11                 @SuppressWarnings("unchecked")
12                 Entry<K,V> e =(Entry<K,V>) x;
13                 int i = indexFor(e.hash, table.length);
14                 Entry<K,V> prev = table[i];
15                 Entry<K,V> p = prev;
16                 while(p !=null){
17                     Entry<K,V>next= p.next;
18                     if(p == e){
19                         if(prev == e)
20                             table[i]=next;
21                         else
22                             prev.next=next;
23                         e.value =null;// 有助于垃圾回收
24                         size--;
25                         break;
26                     }
27                     prev = p;
28                     p =next;
29                 }
30             }
31         }
32     }

    从源码中可以看出,首先会遍历queue队列,队列中保存的元素是已经被垃圾回收的元素的key值。然后将该key从table中删除。这步方法在getTable,resize,size方法中使用到,并且在垃圾回收过程中也会使用,因此他是在多线程环境下调用的,所以该方法使用了同步方法,确保线程安全。

    那么是谁将垃圾回收的后的元素放入queue中的呢?这就要从Entry的结构说起。

  4、Entry结构介绍

    WeakHashMap的Entry是Reference的子类。Entry实例化时会引用当前的queue,如果当前Entry被垃圾回收后,会将key注册的queue中。在后文中会详细介绍Reference类。

 1     /**
 2      * The entries in this hash table extend WeakReference, using its main ref
 3      * field as the key.
 4      */
 5     private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
 6         V value;
 7         final int hash;
 8         Entry<K,V> next;
 9 
10         /**
11          * Creates new entry.
12          */
13         Entry(Object key, V value,
14               ReferenceQueue<Object> queue,
15               int hash, Entry<K,V> next) {
16             super(key, queue);
17             this.value = value;
18             this.hash  = hash;
19             this.next  = next;
20         }
21 
22         @SuppressWarnings("unchecked")
23         public K getKey() {
24             return (K) WeakHashMap.unmaskNull(get());
25         }
26 
27         public V getValue() {
28             return value;
29         }
30 
31         public V setValue(V newValue) {
32             V oldValue = value;
33             value = newValue;
34             return oldValue;
35         }
36 
37         public boolean equals(Object o) {
38             if (!(o instanceof Map.Entry))
39                 return false;
40             Map.Entry<?,?> e = (Map.Entry<?,?>)o;
41             K k1 = getKey();
42             Object k2 = e.getKey();
43             if (k1 == k2 || (k1 != null && k1.equals(k2))) {
44                 V v1 = getValue();
45                 Object v2 = e.getValue();
46                 if (v1 == v2 || (v1 != null && v1.equals(v2)))
47                     return true;
48             }
49             return false;
50         }
51 
52         public int hashCode() {
53             K k = getKey();
54             V v = getValue();
55             return Objects.hashCode(k) ^ Objects.hashCode(v);
56         }
57 
58         public String toString() {
59             return getKey() + "=" + getValue();
60         }
61     }

  5、

四、弱键引用

  WeakHashMap的重点是对元素进行垃圾回收的部分。下面将结合源码进行讲解。

  1、源码

  1 public abstract class Reference<T> {
  2 
  3 
  4     private T referent;         /* Treated specially by GC */
  5 
  6     // 将回收的元素添加到队列中。
  7     volatile ReferenceQueue<? super T> queue;
  8 
  9     
 10     @SuppressWarnings("rawtypes")
 11     volatile Reference next;
 12 
 13    
 14     transient private Reference<T> discovered;  /* used by VM */
 15 
 16     
 17     static private class Lock { }
 18     private static Lock lock = new Lock();
 19 
 20 
 21     // 垃圾收集器将回收的引用加入队列
 22     private static Reference<Object> pending = null;
 23 
 24     /* High-priority thread to enqueue pending References
 25      */
 26     private static class ReferenceHandler extends Thread {
 27 
 28         private static void ensureClassInitialized(Class<?> clazz) {
 29             try {
 30                 Class.forName(clazz.getName(), true, clazz.getClassLoader());
 31             } catch (ClassNotFoundException e) {
 32                 throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
 33             }
 34         }
 35 
 36         static {
 37             // pre-load and initialize InterruptedException and Cleaner classes
 38             // so that we don't get into trouble later in the run loop if there's
 39             // memory shortage while loading/initializing them lazily.
 40             ensureClassInitialized(InterruptedException.class);
 41             ensureClassInitialized(Cleaner.class);
 42         }
 43 
 44         ReferenceHandler(ThreadGroup g, String name) {
 45             super(g, name);
 46         }
 47 
 48         public void run() {
 49             while (true) {
 50                 tryHandlePending(true);
 51             }
 52         }
 53     }
 54 
 55     
 56     static boolean tryHandlePending(boolean waitForNotify) {
 57         Reference<Object> r;
 58         Cleaner c;
 59         try {
 60             synchronized (lock) {
 61                 if (pending != null) {
 62                     r = pending;
 63                     // 'instanceof' might throw OutOfMemoryError sometimes
 64                     // so do this before un-linking 'r' from the 'pending' chain...
 65                     c = r instanceof Cleaner ? (Cleaner) r : null;
 66                     // unlink 'r' from 'pending' chain
 67                     pending = r.discovered;
 68                     r.discovered = null;
 69                 } else {
 70                     // The waiting on the lock may cause an OutOfMemoryError
 71                     // because it may try to allocate exception objects.
 72                     if (waitForNotify) {
 73                         lock.wait();
 74                     }
 75                     // retry if waited
 76                     return waitForNotify;
 77                 }
 78             }
 79         } catch (OutOfMemoryError x) {
 80             // Give other threads CPU time so they hopefully drop some live references
 81             // and GC reclaims some space.
 82             // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
 83             // persistently throws OOME for some time...
 84             Thread.yield();
 85             // retry
 86             return true;
 87         } catch (InterruptedException x) {
 88             // retry
 89             return true;
 90         }
 91 
 92         // Fast path for cleaners
 93         if (c != null) {
 94             c.clean();
 95             return true;
 96         }
 97 
 98         ReferenceQueue<? super Object> q = r.queue;
 99         if (q != ReferenceQueue.NULL) q.enqueue(r);
100         return true;
101     }
102 
103     static {
104         ThreadGroup tg = Thread.currentThread().getThreadGroup();
105         for (ThreadGroup tgn = tg;
106              tgn != null;
107              tg = tgn, tgn = tg.getParent());
108         Thread handler = new ReferenceHandler(tg, "Reference Handler");
109         /* If there were a special system-only priority greater than
110          * MAX_PRIORITY, it would be used here
111          */
112         handler.setPriority(Thread.MAX_PRIORITY);
113         handler.setDaemon(true);
114         handler.start();
115 
116         // provide access in SharedSecrets
117         SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
118             @Override
119             public boolean tryHandlePendingReference() {
120                 return tryHandlePending(false);
121             }
122         });
123     }
124 
125     /* -- Referent accessor and setters -- */
126 
127     public T get() {
128         return this.referent;
129     }
130 
131     
132     public void clear() {
133         this.referent = null;
134     }
135 
136 
137     /* -- Queue operations -- */
138 
139     public boolean isEnqueued() {
140         return (this.queue == ReferenceQueue.ENQUEUED);
141     }
142 
143     
144     public boolean enqueue() {
145         return this.queue.enqueue(this);
146     }
147 
148 
149     /* -- Constructors -- */
150 
151     Reference(T referent) {
152         this(referent, null);
153     }
154 
155     Reference(T referent, ReferenceQueue<? super T> queue) {
156         this.referent = referent;
157         this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
158     }
159 
160 }

  2、Reference的实现原理

    第一步通过静态代码块启动ReferenceHandler线程,所有Reference实例共享该线程。pending会被虚拟机调用,当有元素被垃圾回收以后,会添加到该队列中。Reference线程通过无线循环检查pending是否存在元素。当发现有元素被垃圾回收以后,会将该元素添加到queue中。如果当前没有发现被回收的元素,也就是pending为null时,会通过lock.wait() 阻塞线程。直到有元素被回收以后,会调用nodify唤醒线程。

  3、案例

    该过程涉及到多线程的知识,jvm垃圾回收的原理等,这部分比较复杂,暂时无法很好的讲解。将回收的元素加入到queue队列的部分实现用了消费者模式。为了读者更好的理解该原理,我对他进行模仿,实现了一个简单的消费者模式的demo。

    这个demo主要功能如下:

    (1)多个生产者不断的生产某件产品,当产品数量大于10以后就停止生产,只要当产品数量小于10,就会生产。
    (2)多个消费者不断的消费产品,知道消费结束。
    (3)当生产者生产到10件以后,就阻塞生产线,通知消费者消费。
    (4)当消费者消费完结束以后,阻塞消费,通知生产者生产。

  1 public class ProducerCustomerDemo{
  2         private static int index =0;
  3         private static int size =0;
  4         static private class Lock{};
  5         private static Lock lock= new Lock();
  6         private static Entry head =null;
  7         public synchronized int getSize(){
  8             return size;
  9         }
 10         public synchronized void addSize(){
 11             ProducerCustomerDemo.size++;
 12         }
 13         public synchronized void minusSize(){
 14             ProducerCustomerDemo.size--;
 15         }
 16         public synchronized int getIndex(){
 17             return index;
 18         }
 19         public synchronized void addIndex(){
 20             index++;
 21         }
 22         public synchronized void minusIndex(){
 23             index--;
 24         }
 25         public static void main(String[] args){
 26             new Thread(new ProducerCustomerDemo().new Consumer("aaa")).start();
 27             new Thread(new ProducerCustomerDemo().new Consumer("bbb")).start();
 28             new Thread(new ProducerCustomerDemo().new Consumer("ccc")).start();
 29             new Thread(new ProducerCustomerDemo().new Consumer("ddd")).start();
 30             new Thread(new ProducerCustomerDemo().new Producer("张三")).start();
 31             new Thread(new ProducerCustomerDemo().new Producer("李四")).start();
 32         }
 33         class Entry{
 34             Entry next;
 35             int value;
 36             public Entry(int value){
 37                 this.value = value;
 38             }
 39         }
 40         class Consumer implements Runnable{
 41             private String name;
 42             public Consumer(String name){
 43                 this.name = name;
 44             }
 45             @Override
 46             public void run(){
 47                 for(;;){
 48                     Entry temp =null;
 49                     synchronized(lock){
 50                         if(head !=null){
 51                             try{
 52                                 Thread.sleep(1000);
 53                             }catch(InterruptedException e){
 54                                 e.printStackTrace();
 55                             }
 56                             temp = head;
 57                             head = temp.next;
 58                             temp.next=null;
 59                             minusSize();
 60                             System.out.println("消费,当前产品数"+getSize()+"--"+name+":"+ temp.value);
 61                             lock.notify();
 62                         }else{
 63                             try{
 64                                 lock.wait();
 65                             }catch(InterruptedException e){
 66                                 e.printStackTrace();
 67                             }
 68                         }
 69                     }
 70                 }
 71             }
 72         }
 73         class Producer implements Runnable{
 74             private String name;
 75             public Producer(String name){
 76                 this.name = name;
 77             }
 78             @Override
 79             public void run(){
 80                 for(;;){
 81                     synchronized(lock){
 82                         if(getSize()<10){
 83                             try{
 84                                 Thread.sleep(1000);
 85                             }catch(InterruptedException e){
 86                                 e.printStackTrace();
 87                             }
 88                             addIndex();
 89                             Entry temp =new Entry(getIndex());
 90                             temp.next= head;
 91                             head = temp;
 92                             addSize();
 93                             System.out.println("生产,当前产品数"+getSize()+"--"+ name +":"+ temp.value);
 94                             lock.notify();
 95                         }else{
 96                             try{
 97                                 lock.wait();
 98                             }catch(InterruptedException e){
 99                                 e.printStackTrace();
100                             }
101                         }
102                     }
103                 }
104             }
105         }
106     }

五、

原文地址:https://www.cnblogs.com/niujifei/p/14750694.html