springboot源码学习(一)- ConcurrentReferenceHashMap深度解读
一、前言最近一直在阅读springboot的启动源码,它通过加载jar包中的META-INF/spring.factories文件,通过反射实例化其实现类,实现自动装配。其中有一个数据结构被设计来缓存从spring.factorys中读取的接口及其实现类,即:ConcurrentReferenceHashMap,拿出来给大家分享一下。二、主要功能支持高并发,基于分段锁实现,性能优于hashTabl
一、前言
最近一直在阅读springboot的启动源码,它通过加载jar包中的META-INF/spring.factories文件,通过反射实例化其实现类,实现自动装配。其中有一个数据结构被设计来缓存从spring.factorys中读取的接口及其实现类,即:ConcurrentReferenceHashMap,拿出来给大家分享一下。
二、主要功能
- 支持高并发,基于分段锁实现,性能优于hashTable
- 和ConcurrentHashMap设计约束一致,但是支持key和value都为null
- 默认引用方式为弱引用,非常适合用来做缓存
- 发生gc的时候,任何时候的缓存对象有可能会被回收,相当于有一个线程在删除内存中的对象
三、名词简介
- 分段锁
-
HashTable容器效率低下是因为所有访线程都必须竞争同一把锁,那如果把容器中的一把锁变为多把锁,每一把锁锁容器中一部分数据,那么当多个线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这个就是分段锁。
-
ConcurrentHashMap即ConcurrentMap中的一个实现类也是采用分段锁实现的。它是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。示意图如下:

- java引用
- 强引用:java中最常见的引用,除非把引用显式的设置为null才有可能被gc回收
//date即为对象的强引用
Date date = new Date();
- 弱引用:强度弱于强引用,通过类 SoftReference 来表示。它的作用是告诉垃圾回收器,程序中的哪些对象是不那么重要,当内存不足的时候是可以被暂时回收的。当 JVM 中的内存不足的时候,垃圾回收器会释放那些只被软引用所指向的对象。如果全部释放完这些对象之后,内存还不足,才会抛出 OutOfMemory 错误。软引用非常适合于创建缓存。
import org.apache.commons.io.FileUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.lang.ref.SoftReference;
public class FileContentCache {
private String path;
private SoftReference<String> dataRef;
public FileContentCache(String path) {
this.path = path;
this.dataRef = new SoftReference<>("");
}
private String readFile() throws IOException {
return FileUtils.readFileToString(new File(path),"utf-8");
}
public String getFileContent() throws IOException {
String content = dataRef.get();
if(StringUtils.isEmpty(content)){
content = readFile();
dataRef = new SoftReference<>(content);
}
return content;
}
}
- 软引用:在强度上弱于软引用,通过类 WeakReference 来表示。它的作用是引用一个对象,但是并不阻止该对象被回收。弱引用的作用在于解决强引用所带来的对象之间在存活时间上的耦合关系。弱引用最常见的用处是在集合类中,尤其在哈希表中。哈希表的接口允许使用任何 Java 对象作为键来使用。当一个键值对被放入到哈希表中之后,哈希表对象本身就有了对这些键和值对象的引用。如果这种引用是强引用的话,那么只要哈希表对象本身还存活,其中所包含的键和值对象是不会被回收的。如果某个存活时间很长的哈希表中包含的键值对很多,最终就有可能消耗掉 JVM 中全部的内存。对于这种情况的解决办法就是使用弱引用来引用这些对象,这样哈希表中的键和值对象都能被垃圾回收。Java 中提供了 WeakHashMap 来解决这一常见问题。
//weakHashMap即为软引用,gc的就有可能回收
WeakHashMap<String, String> weakHashMap = new WeakHashMap<>();
- 幽灵引用:主要用来实现比较精细的内存使用控制,可以使得程序所消耗的内存维持在一个相对较低的数量。在Object 类里面有个 finalize 方法,其设计的初衷是在一个对象被真正回收之前,可以用来执行一些清理的工作。因为 Java 并没有提供类似 C++ 的析构函数一样的机制,就通过 finalize 方法来实现。但是问题在于垃圾回收器的运行时间是不固定的,所以这些清理工作的实际运行时间也是不能预知的。幽灵引用(phantom reference)可以解决这个问题。在创建幽灵引用 PhantomReference 的时候必须要指定一个引用队列。当一个对象的 finalize 方法已经被调用了之后,这个对象的幽灵引用会被加入到队列中。通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了。
public class PhantomBuffer {
private byte[] data = new byte[0];
private ReferenceQueue<byte[]> queue = new ReferenceQueue<byte[]>();
private PhantomReference<byte[]> ref = new PhantomReference<byte[]>(data, queue);
public byte[] get(int size) {
if (size <= 0) {
throw new IllegalArgumentException("Wrong buffer size");
}
if (data.length < size) {
data = null;
// 调用垃圾回收器
System.gc();
try {
// 该方法会阻塞直到队列非空
queue.remove();
// 幽灵引用需要要人为清空
ref.clear();
ref = null;
data = new byte[size];
ref = new PhantomReference<byte[]>(data, queue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return data;
}
}
四、源码解析
ConcurrentReferenceHashMap继承了AbstractMap,实现了ConcurrentMap,运用了分段锁技术提高并发访问效率。
public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
//默认初始化容量
private static final int DEFAULT_INITIAL_CAPACITY = 16;
//默认加载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
//默认并发级别
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//引用类型,默认弱引用
private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT;
//最大并发级别
private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16;
//分段最大值
private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30;
//分段数组
private final Segment[] segments;
//加载因子
private final float loadFactor;
//引用类型
private final ReferenceType referenceType;
//用于计算段数组大小和哈希索引的移位值
private final int shift;
//键值对,key和value可以为null
@Nullable
private volatile Set<Map.Entry<K, V>> entrySet;
//构造器
public ConcurrentReferenceHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
}
...
//分段类实现了读写锁
protected final class Segment extends ReentrantLock {
...
}
//由于存储key-value值
protected static final class Entry<K, V> implements Map.Entry<K, V> {
...
}
}
这个是ConcurrentReferenceHashMap的源码,采用了分段技术来支持高并发,内部维持了一个Segment数组,每个Segment成员都实现了读写锁;和一个Entry集合,用于存储key-value,其实现方式和ConcurrentHashMap类似。里面默认为软引用,可以用来缓存一些初始化和临时数据,可以直接被垃圾回收器回收,不需要设置一个定时线程来清空。这个类是一个高并发的安全的软引用或者弱引用的经典缓存实现。
如果觉得文章可以的话,希望不吝给个关注;如果有问题的话,希望大佬批评指正,谢谢!
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)