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都是强引用,强引用具备以下特点
- 强引用可以直接访问目标对象
- 强引用所指向的对象在任何时候都不会被系统回收,JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
- 强引用可能导致内存泄露
- 软引用
软引用是可被回收的引用,但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系统内存使用状况无关
来源:oschina
链接:https://my.oschina.net/u/3768341/blog/4284299