栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > Java

ConcurrentHashMap在并发场景下使用的优化实践

Java 更新时间:发布时间: 百科书网 趣学号

目录

业务场景

第一版代码实现

性能分析

第二版代码实现


业务场景

按门店分类存储订单信息,维护在一个ConcurrentHashMap的结构中,实际业务中存在并发。所以功能的实现要考虑从map结构中取值操作关于value的非空判断和初始化操作的并发安全问题。

功能实现可以忽略从Map取出ConcurrentlinkedQueue队里和往队列放订单这两步操作的原子性,业务中允许订单顺序存在一定误差。

第一版代码实现

之前有文章写过ConcurrentHashMap并发场景下的使用方式,可以基于computeIfAbsent方法实现Map的取值非空判断和初始化操作的原子性从而实现并发安全。

computeIfAbsent方法通过加锁的方式保证从ConcurrentHashMap取出ConcurrentlinkedQueue队列的操作是原子操作。

public class OrderService{

	public static Map> shopOrderMap = new ConcurrentHashMap();
	
	public void setWaitMakeQueue(MakeOrder makeOrder){
		try {
			//原子操作取出门店的ConcurrentlinkedQueue队列
			ConcurrentlinkedQueue list = this.shopOrderMap.computeIfAbsent(key, key1 -> new ConcurrentlinkedQueue<>());
			//此操作允许与取出ConcurrentlinkedQueue队列非原子操作带来的顺序误差
            list.add(makeOrder);
		} catch (Exception e) {
			log.error("订单进入待制作队列异常", e);
		} 
	}
}

性能分析

上面代码中锁的目的是保证非空判断和初始化操作的原子性,也就是同一个key的value只会被初始化一次。但是在实际业务场景中由于ConcurrentHashMap中每个门店的队列只会初始化一次,所以非空判断的逻辑其实执行的比例其实很少当门店订单队列已经初始化过后,由于ConcurrentHashMap本身的get操作是线程安全的,那就没必要再加锁了,所以从性能考虑的话整体加锁的实现并不是很好。 

第二版代码实现

类似于双重校验单例模式的实现,通过双重校验+volatile实现同步块代码量的最小化提升性能。当门店订单队列已经初始化过后,执行无锁部分的代码借助CAS原子操作就完成放入队列的操作了,只有当门店队列未初始化时才会加锁。

 但是由于订单队列是Map结构中的Value无法通过volatile关键字修饰,所以在双重校验时需要重新获取一次用于第二次校验。

public class ShopLock{
    private static Map map = new ConcurrentHashMap();

    public static ReentrantLock getShopLock(String key){
        return map.computeIfAbsent(key,k -> new ReentrantLock());
    }
}

public class OrderService{

	public static Map> shopOrderMap = new ConcurrentHashMap();
	
	public void setWaitMakeQueue(MakeOrder makeOrder){
		try {
			//首先不加锁获取一次门店的订单存储队列
			ConcurrentlinkedQueue list = this.shopOrderMap.get(key);
			if(list != null){
				list.add(makeOrder);
				return;
			}
			//加本地门店ReentrantLock锁,目的是保证取出list和list.add(makeOrder)操作的原子性
			ShopLock.getShopLock(key).lock();
			//因为订单存储队列是Map的Value,无法通过volatile实现可见性,所以加锁后需要重新重新获取门店的订单存储队列,用于队列的双重校验
			list = this.shopOrderMap.get(key);
			if(list != null){
				list.add(makeOrder);
				return;
			}
			//双重校验都为空后初始化存储队列,放入订单后存到ConcurrentHashMap结构中
			list = new ConcurrentlinkedQueue<>(); 
			list.add(makeOrder);
			this.shopOrderMap.put(key, list)
		} catch (Exception e) {
			log.error("订单进入待制作队列异常", e);
		} finally {
			//释放门店锁
			ShopLock.getLock(key).unlock();
		}
	}
}

转载请注明:文章转载自 www.051e.com
本文地址:http://www.051e.com/it/274928.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 ©2023-2025 051e.com

ICP备案号:京ICP备12030808号