zoukankan      html  css  js  c++  java
  • 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     }

    五、

  • 相关阅读:
    下拉框Html.DropDownList 和DropDownListFor 的经常用法
    39个让你受益的HTML5教程
    RapeLay(电车之狼R)的结局介绍 (隐藏结局攻略)
    BP神经网络算法学习
    java实现第七届蓝桥杯取球博弈
    java实现第七届蓝桥杯剪邮票
    java实现第七届蓝桥杯剪邮票
    java实现第七届蓝桥杯剪邮票
    java实现第七届蓝桥杯剪邮票
    java实现第七届蓝桥杯抽签
  • 原文地址:https://www.cnblogs.com/niujifei/p/14750694.html
Copyright © 2011-2022 走看看