Sentinel源码中map.put直接添加与创建新HashMap替换旧Map的区别
问题
我在阅读Sentinel的源码时发现,当需要向Map中添加条目时,并未直接使用map.put方法,而是创建一个新的HashMap来替换旧Map,示例代码如下:
public class NodeSelectorSlot extends AbstractLinkedProcessorSlot<Object> { private volatile Map<String, DefaultNode> map = new HashMap<String, DefaultNode>(10); @Override public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) throws Throwable { DefaultNode node = map.get(context.getName()); if (node == null) { synchronized (this) { node = map.get(context.getName()); if (node == null) { node = new DefaultNode(resourceWrapper, null); // create a new hashmap HashMap<String, DefaultNode> cacheMap = new HashMap<String, DefaultNode>(map.size()); cacheMap.putAll(map); cacheMap.put(context.getName(), node); map = cacheMap; ((DefaultNode) context.getLastNode()).addChild(node); } } } context.setCurNode(node); fireEntry(context, resourceWrapper, node, count, prioritized, args); } ... }
请问这两种实现方式之间有什么区别?
回答
这两种写法的核心差异体现在并发场景下的性能、线程安全保障逻辑上,具体区别如下:
线程安全的实现逻辑不同
如果直接用map.put,普通HashMap本身非线程安全,必须给整个put操作加锁,这会导致所有读写操作都阻塞在锁上;而Sentinel的写法采用拷贝替换+volatile:读操作完全无锁(map被volatile修饰,保证多线程下的可见性),只有新增节点时才加锁做拷贝替换,锁仅在写操作时生效,读操作全程不受影响。读写性能差异明显
高并发场景下,直接加锁的put会让所有读请求等待锁释放,性能瓶颈突出;而拷贝替换的方式,读操作无锁并行,只有写操作短暂持有锁,整体读写吞吐量更高。数据一致性保障不同
直接put在锁保护下能保证单个操作原子性,但多线程操作时,读请求可能看到Map的中间更新状态;而拷贝替换的方式中,map引用的切换是原子性的(volatile特性),读请求要么看到旧的完整Map,要么看到新的完整Map,不会出现半更新的不一致状态。内存开销有区别
拷贝替换每次新增都要创建新HashMap并复制旧数据,会带来额外内存开销和短暂GC压力;直接put没有这部分开销,内存利用更高效。
内容的提问来源于stack exchange,提问作者moreHope
相关产品推荐
相关产品推荐

