ConcurrentHashMap的使用场景

在一次项目中,需要设计一个map存储线程资源池,为每种请求类型创建一个线程池。当该类型所对应的线程池中的所有线程都达到工作饱和状态,即是没有多余可用线程,则自动丢弃该类型的请求,以此来保证其他类型的请求不受影响。这中设计属于资源隔离中的线程池隔离,在微服务系统属于是很常见的场景。

这种设计没什么难度,初始化一个成员变量map。当请求到达时,检查map中是否已经存在创建好的线程池即可,如果存在则返回,如果不存在就创建一个新的线程池放入map中,同时返回新创建的线程池。在多线程的环境中,检查map时加上锁关键字synchronized即可。代码块如下:
public ThreadPool getThreadPool(String type) {

    RingBuffer<StringEvent> disruptor = threadPoolMap.get(type);
    if (disruptor == null) {
        synchronized (this) {
            disruptor = threadPoolMap.get(type);
            if (disruptor == null) {
                threadPoolMap.put(type, createThreadPool(type));
            }
        }
    }
    return threadPoolMap.get(type);

}
上面的代码中是完全正确的,但是当你了解过ConcurrentHashMap的原理后,你就会觉得ConcurrentHashMap的性能会比单纯的使用synchronized+hashMap高很多。
在上面的代码中,第一次创建时,只会有一个进程进入到synchronized的代码块,而CHashMap则会只synchronized其中的某一个segment。其二上面的代码读起来不优雅,大if语句中嵌套小if 。
CHashMap解决的就是上面这种场景,在多线程的环境中,其API  putIfAbsent 方法内部就会帮你处理并发的问题。调用该API时如果map中已经存在Entry,返回值就是该Entry ,如果map中不存在,就会返回null。
这样代码是不是就简便很多了。如下:
    public ThreadPool getThreadPool(String type) {

    RingBuffer<StringEvent> disruptor = threadPoolMap.get(type);
    if (disruptor == null) {
        threadPoolMap.putIfAbent(type, createThreadPool(type));
       }
    }
    return threadPoolMap.get(type);

}
map中不存在,我就创建一个线程池放进去,然后取出返回。反正CHashMap会在putIfAben内部处理并发情况,同时只会有一个线程put成功。甚至都不需要检查map中是否已经存在entry,反正putIfAbent方法会检查。

如果不进行高性能压测,不观察内存中的线程池资源情况,代码编写可以到此结束,代码不但优雅了许多(避免了if中嵌套if)还减少了三行代码。同一个功能,代码量越小出bug的概率越小(不知道是哪位大牛说的)。但是如果进行高并发量压测就会发现,内存中会飙升很高的WAITING 线程,多少并发量就会有多少倍的线程数,同时只会有一个的线程池在工作,其他线程都是WAITING状态。例如:设置50个统一类型的请求同时访问,正常情况下应该只有一个线程池被创建。真实情况是有50个线程池被创建,但是只会有一个线程池在工作,其他线程池都是WAITING。

问题出现在threadPoolMap.putIfAbent(type,createThreadPool(type))这一行上,putIfAbent API 本身没什么问题,情况就出在createThreadPool(type)上。代码的功能是先创建一个线程池然后存放到threadPoolMap里,这时CHashMap会保证只有一个线程池存放成功,但存放失败的线程池已经创建出来了,只是没有存放到threadPoolMap里罢了。

至此,原因水落石出了。50个并发请求同时过来,会创建50个线程池,但是只会有一个1线程池被存放到CHashMap中,另外49个都会被丢弃。但是线程池已经创建成功了,所以这49个都是WAITING状态。

综上所述:
ConcurrentHashMap本身是一个线程安全的容器,其putIfAbent也没有问题。但是其不适合保存创建计算机资源的场景。因为计算机资源诸如:线程池、IO等都是有限的,如果还不涉及到自动回收的话就更宝贵了。创建成功又不被存放到CHashMap中,就不会被使用到,就造成了浪费。ChashMap可以使用在创建普通POJO对象的场景中,JVM的GC会自动回收无效的引用。

可见,并不是所有的优雅代码都能优雅的运行的。

更多优雅代码:https://github.com/wingedFish


  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值