前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring扩展的集合LinkedMultiValueMap和ConcurrentReferenceHashMap解析

Spring扩展的集合LinkedMultiValueMap和ConcurrentReferenceHashMap解析

作者头像
算法之名
发布2020-05-26 15:17:41
1.6K0
发布2020-05-26 15:17:41
举报
文章被收录于专栏:算法之名

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

代码语言:javascript
复制
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>>来处理所有的方法。

代码语言:javascript
复制
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();
   }

}

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

代码语言:javascript
复制
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原理解析

代码语言:javascript
复制
private final Segment[] segments;
代码语言:javascript
复制
protected final class Segment extends ReentrantLock

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

代码语言:javascript
复制
private final ReferenceType referenceType; //引用类型
代码语言:javascript
复制
public enum ReferenceType {

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

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

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

  • 强引用
代码语言:javascript
复制
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实例的强引用。

此时修改代码

代码语言:javascript
复制
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. 强引用可能导致内存泄露
  4. 软引用

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

代码语言:javascript
复制
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

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

代码语言:javascript
复制
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()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。

代码语言:javascript
复制
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里面的。

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

代码语言:javascript
复制
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清理掉的

代码语言:javascript
复制
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系统内存使用状况无关

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档