Spring扩展的集合LinkedMultiValueMap和ConcurrentReferenceHashMap解析

半腔热情 提交于 2020-10-26 05:40:16

MultiValueMap是一个接口,它的一个键可以对应多个值(列表)

public interface MultiValueMap<K, V> extends Map<K, List<V>> {

   /**
    * 获取key的第一个值
    */
   @Nullable
   V getFirst(K key);

   /**
    * 添加value到key对应值的列表中
    */
   void add(K key, @Nullable V value);

   /**
    * 添加一个列表到key对应的值中
    */
   void addAll(K key, List<? extends V> values);

   /**
    * 添加另一个MultiValueMap到本对象中
    */
   void addAll(MultiValueMap<K, V> values);

   /**
    * 给key设置一个给定的值value
    */
   void set(K key, @Nullable V value);

   /**
    * 将Map所有key,value设置进去
    */
   void setAll(Map<K, V> values);

   /**
    * 转化为一个单值Map
    */
   Map<K, V> toSingleValueMap();

}

该接口的实现类为LinkedMultiValueMap,它其实就是委托了一个LinkedHashMap<K, List<V>>来处理所有的方法。

public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializable, Cloneable {

   private static final long serialVersionUID = 3801124242820219131L;

   private final Map<K, List<V>> targetMap;

   public LinkedMultiValueMap() {
      this.targetMap = new LinkedHashMap<>();
   }

   public LinkedMultiValueMap(int initialCapacity) {
      this.targetMap = new LinkedHashMap<>(initialCapacity);
   }

   public LinkedMultiValueMap(Map<K, List<V>> otherMap) {
      this.targetMap = new LinkedHashMap<>(otherMap);
   }


   // MultiValueMap implementation

   @Override
   @Nullable
   public V getFirst(K key) {
      List<V> values = this.targetMap.get(key);
      return (values != null && !values.isEmpty() ? values.get(0) : null);
   }

   @Override
   public void add(K key, @Nullable V value) {
      List<V> values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>());
      values.add(value);
   }

   @Override
   public void addAll(K key, List<? extends V> values) {
      List<V> currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>());
      currentValues.addAll(values);
   }

   @Override
   public void addAll(MultiValueMap<K, V> values) {
      for (Entry<K, List<V>> entry : values.entrySet()) {
         addAll(entry.getKey(), entry.getValue());
      }
   }

   @Override
   public void set(K key, @Nullable V value) {
      List<V> values = new LinkedList<>();
      values.add(value);
      this.targetMap.put(key, values);
   }

   @Override
   public void setAll(Map<K, V> values) {
      values.forEach(this::set);
   }

   @Override
   public Map<K, V> toSingleValueMap() {
      LinkedHashMap<K, V> singleValueMap = new LinkedHashMap<>(this.targetMap.size());
      this.targetMap.forEach((key, values) -> {
         if (values != null && !values.isEmpty()) {
            singleValueMap.put(key, values.get(0));
         }
      });
      return singleValueMap;
   }


   // Map implementation

   @Override
   public int size() {
      return this.targetMap.size();
   }

   @Override
   public boolean isEmpty() {
      return this.targetMap.isEmpty();
   }

   @Override
   public boolean containsKey(Object key) {
      return this.targetMap.containsKey(key);
   }

   @Override
   public boolean containsValue(Object value) {
      return this.targetMap.containsValue(value);
   }

   @Override
   @Nullable
   public List<V> get(Object key) {
      return this.targetMap.get(key);
   }

   @Override
   @Nullable
   public List<V> put(K key, List<V> value) {
      return this.targetMap.put(key, value);
   }

   @Override
   @Nullable
   public List<V> remove(Object key) {
      return this.targetMap.remove(key);
   }

   @Override
   public void putAll(Map<? extends K, ? extends List<V>> map) {
      this.targetMap.putAll(map);
   }

   @Override
   public void clear() {
      this.targetMap.clear();
   }

   @Override
   public Set<K> keySet() {
      return this.targetMap.keySet();
   }

   @Override
   public Collection<List<V>> values() {
      return this.targetMap.values();
   }

   @Override
   public Set<Entry<K, List<V>>> entrySet() {
      return this.targetMap.entrySet();
   }

   public LinkedMultiValueMap<K, V> deepCopy() {
      LinkedMultiValueMap<K, V> copy = new LinkedMultiValueMap<>(this.targetMap.size());
      this.targetMap.forEach((key, value) -> copy.put(key, new LinkedList<>(value)));
      return copy;
   }

   @Override
   public LinkedMultiValueMap<K, V> clone() {
      return new LinkedMultiValueMap<>(this);
   }

   @Override
   public boolean equals(Object obj) {
      return this.targetMap.equals(obj);
   }

   @Override
   public int hashCode() {
      return this.targetMap.hashCode();
   }

   @Override
   public String toString() {
      return this.targetMap.toString();
   }

}

我们来看看这一对接口实现类的简单应用

public class SunTest {
    public static void main(String[] args) {
        MultiValueMap<String,String> map = new LinkedMultiValueMap<>();
        map.add("2","1");
        map.add("2","2");
        map.add("1","1");
        map.add("1","2");
        map.entrySet().stream().forEach(entry -> System.out.println(entry.getKey() + ":" + entry.getValue().toString()));
    }
}

结果

2:[1, 2]
1:[1, 2]

根据LinkedHashMap源码分析 我们知道LinkedHashMap是可以对数据进行按照插入顺序排序的,所以LinkedMultiValueMap保留了这一性质,但只是对插入的Key的先后顺序进行排序。

ConcurrentReferenceHashMap是一个线程安全的Map,它采用的是JDK 1.7的ConcurrentHashMap的分段锁来做并发控制的。当然跟JDK 1.8是不同的,有关1.8的ConcurrentHashMap可以参考ConcurrentHashMap 1.8原理解析

private final Segment[] segments;
protected final class Segment extends ReentrantLock

那么ConcurrentReferenceHashMap跟JDK 1.7的ConcurrentHashMap最大的不同就是

private final ReferenceType referenceType; //引用类型
public enum ReferenceType {

   /** Use {@link SoftReference SoftReferences}. */
   SOFT,

   /** Use {@link WeakReference WeakReferences}. */
   WEAK
}

一般来说,Java包含四种引用类型——强引用、弱引用,软引用和虚引用,不过ConcurrentReferenceHashMap这里只给出了软引用和弱引用。

  • 强引用
public class ReferenceTest {
    public static void main(String[] args) {
        ReferenceTest test = new ReferenceTest();
        System.out.println(test.hello("HelloWorld"));
    }

    public String hello(String arg) {
        StringBuilder builder = new StringBuilder(arg);
        return builder.toString();
    }
}

结果

HelloWorld

此时的局部变量表会存放builder对象

StringBuilder builder = new StringBuilder(arg);是在hello()方法中运行的,所以局部变量builder将分配在栈上,而对象StringBuilder实例被分配在堆上。局部变量builder指向StringBuilder实例所在的堆空间,通过builder可以操作该实例,那么builder就是StringBuilder实例的强引用。

此时修改代码

public class ReferenceTest {
    public static void main(String[] args) {
        ReferenceTest test = new ReferenceTest();
        System.out.println(test.hello("HelloWorld"));
    }

    public String hello(String arg) {
        StringBuilder builder = new StringBuilder(arg);
        StringBuilder builder1 = builder;
        System.out.println(builder1.toString());
        return builder.toString();
    }
}

那么,builder所指向的对象也将被builder1所指向,同时在局部变量表上会分配空间存放builder1变量。此时StringBuilder示例就有两个引用。

对引用的"=="操作用于表示两操作数所指向的堆空间地址是否相同,不表示两操作数所指向的对象是否相等。

而以上的builder和builder1都是强引用,强引用具备以下特点

  1. 强引用可以直接访问目标对象
  2. 强引用所指向的对象在任何时候都不会被系统回收,JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
  3. 强引用可能导致内存泄露
  • 软引用

软引用是可被回收的引用,但GC未必会回收软引用的对象。当内存资源紧张时,软引用对象会被回收。

public class SoftReferenceDemo {

    public static void main(String[] args) throws InterruptedException {
        //100M的缓存数据,强引用
        byte[] cacheData = new byte[100 * 1024 * 1024];
        //将缓存数据用软引用持有
        SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData);
        //将缓存数据的强引用去除
        cacheData = null;
        System.out.println("第一次GC前" + cacheData);
        System.out.println("第一次GC前" + cacheRef.get());
        //进行一次GC后查看对象的回收情况
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + cacheRef.get());

        //在分配一个120M的对象,看看缓存对象的回收情况
        byte[] newData = new byte[120 * 1024 * 1024];
        System.out.println("分配后" + cacheData);
        System.out.println("分配后" + cacheRef.get());
    }

}

添加JVM参数-Xmx200m,运行结果如下

第一次GC前null
第一次GC前[B@7adf9f5f
第一次GC后null
第一次GC后[B@7adf9f5f
分配后null
分配后null

从结果可以看出,当系统分配一个比较大的内存对象后,软引用被清除。

把代码做进一步的修改,用引用队列ReferenceQueue来观察软引用被GC清理,如果被清理会被添加到引用队列中,同时在队列中删除,打印byte[] object is deleted

public class SoftReferenceDemo {
    //此处必须加volatile,否则线程t对主线程的queue的数据不可见
    static volatile ReferenceQueue<byte[]> queue = null;

    static class ByteReference extends SoftReference<byte[]> {
        public ByteReference(byte[] referent, ReferenceQueue<? super byte[]> q) {
            super(referent, q);
        }
    }

    static class CheckRefQueue implements Runnable {
        @Override
        public void run() {
            while (true) {
                if (queue != null) {
                    ByteReference obj = null;
                    try {
                        obj = (ByteReference) queue.remove();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (obj != null) {
                        System.out.println("byte[] object is deleted");
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new CheckRefQueue());
        t.setDaemon(true);
        t.start();
        //100M的缓存数据
        byte[] cacheData = new byte[100 * 1024 * 1024];
        //将缓存数据用软引用持有并绑定引用队列queue
        queue = new ReferenceQueue<>();
        ByteReference cacheRef = new ByteReference(cacheData,queue);
        //将缓存数据的强引用去除
        cacheData = null;
        System.out.println("第一次GC前" + cacheData);
        System.out.println("第一次GC前" + cacheRef.get());
        //进行一次GC后查看对象的回收情况
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + cacheRef.get());

        //在分配一个120M的对象,看看缓存对象的回收情况
        byte[] newData = new byte[120 * 1024 * 1024];
        System.out.println("分配后" + cacheData);
        System.out.println("分配后" + cacheRef.get());
    }
}

JVM运行参数-Xmx200m

运行结果

第一次GC前null
第一次GC前[B@7adf9f5f
第一次GC后null
第一次GC后[B@7adf9f5f
byte[] object is deleted
分配后null
分配后null

  • 弱引用

弱引用是一种比较引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此,并不一定能很快发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册的引用队列中,这一点跟软引用是一样的。

public class WeekReferenctTest {
    @AllArgsConstructor
    @Data
    @ToString
    public static class User {
        private int id;
        private String name;
    }

    static volatile ReferenceQueue<User> userReferenceQueue = null;

    static class CheckRefQueue implements Runnable {
        @Override
        public void run() {
            while (true) {
                if (userReferenceQueue != null) {
                    UserSoftReference obj = null;
                    try {
                        obj = (UserSoftReference) userReferenceQueue.remove();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (obj != null) {
                        System.out.println("user id " + obj.getUid() + " is deleted");
                    }
                }
            }
        }
    }

    @Data
    static class UserSoftReference extends WeakReference<User> {
        int uid;

        public UserSoftReference(User referent, ReferenceQueue<? super User> q) {
            super(referent, q);
            uid = referent.getId();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new CheckRefQueue());
        t.setDaemon(true);
        t.start();
        User u = new User(1,"Jimi");
        userReferenceQueue = new ReferenceQueue<>();
        UserSoftReference userSoftReference = new UserSoftReference(u,userReferenceQueue);
        u = null;
        System.out.println(userSoftReference.get());
        System.gc();
        System.out.println("After GC");
        System.out.println(userSoftReference.get());
    }
}

运行结果

WeekReferenctTest.User(id=1, name=Jimi)
After GC
null
user id 1 is deleted

注意:软引用,弱引用都非常适合来保存那些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当系统资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。

  • 虚引用

虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。

public class TraceCanReliveObj {
    public static TraceCanReliveObj obj;
    static volatile ReferenceQueue<TraceCanReliveObj> phantomQueue = null;

    @SuppressWarnings("unchecked")
    static class CheckRefQueue implements Runnable {
        @Override
        public void run() {
            while (true) {
                if (phantomQueue != null) {
                    PhantomReference<TraceCanReliveObj> objt = null;
                    try {
                        objt = (PhantomReference<TraceCanReliveObj>) phantomQueue.remove();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (objt != null) {
                        System.out.println("TraceCanReliveObj is deleted by GC");
                    }
                }
            }
        }
    }

    /**
     * 允许对象复活一次
     * finalize()只会被调用一次
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("CanReliveObj finalize callled");
        obj = this;
    }

    @Override
    public String toString() {
        return "I am CanRelivedObj";
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new CheckRefQueue());
        t.setDaemon(true);
        t.start();
        phantomQueue = new ReferenceQueue<>();
        //建立一个强引用对象
        obj = new TraceCanReliveObj();
        //将该强引用放入虚引用中,并绑定引用队列
        PhantomReference<TraceCanReliveObj> phantomReference = new PhantomReference<>(obj,phantomQueue);
        //清空强引用
        obj = null;
        System.gc();
        Thread.sleep(1000);
        if (obj == null) {
            System.out.println("obj 是 null");
        }else {
            System.out.println("obj 可用");
        }
        System.out.println("第2次gc");
        obj = null;
        System.gc();
        Thread.sleep(1000);
        if (obj == null) {
            System.out.println("obj 是 null");
        }else {
            System.out.println("obj可用");
        }
    }
}

运行结果

CanReliveObj finalize callled
obj 可用
第2次gc
TraceCanReliveObj is deleted by GC
obj 是 null

由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。

由以上可知,当我们要使用JVM级别高并发缓存的时候,应当使用ConcurrentReferenceHashMap,而不是ConcurrentHashMap,当然前提条件是使用Spring框架,因为ConcurrentReferenceHashMap并不是JDK里面的。

根据软引用和弱引用的性质

public class TestConcurrentReferenceHashMap {
    public static void main(String[] args) throws InterruptedException {
        //对map启用软引用
        Map map = new ConcurrentReferenceHashMap(16, ConcurrentReferenceHashMap.ReferenceType.SOFT);
        map.put("key","val");

        System.out.println(map);

        System.gc();
        Thread.sleep(1000);

        System.out.println(map);

    }
}

运行结果

{key=val}
{key=val}

由于是软引用,此处在JVM内存足够的情况下是不会被gc清理掉的

public class TestConcurrentReferenceHashMap {
    public static void main(String[] args) throws InterruptedException {
        //对map启用弱引用
        Map map = new ConcurrentReferenceHashMap(16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
        map.put("key","val");

        System.out.println(map);

        System.gc();
        Thread.sleep(1000);

        System.out.println(map);

    }
}

运行结果

{key=val}
{}

弱引用只要被gc过都会被垃圾回收,跟JVM系统内存使用状况无关

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!