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都是强引用,强引用具备以下特点
软引用是可被回收的引用,但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系统内存使用状况无关