ConcurrentHashMap的使用场景
在一次项目中,需要设计一个map存储线程资源池,为每种请求类型创建一个线程池。当该类型所对应的线程池中的所有线程都达到工作饱和状态,即是没有多余可用线程,则自动丢弃该类型的请求,以此来保证其他类型的请求不受影响。这中设计属于资源隔离中的线程池隔离,在微服务系统属于是很常见的场景。这种设计没什么难度,初始化一个成员变量map。当请求到达时,检查map中是否已经存在创建好的线程池即可,如果
·
在一次项目中,需要设计一个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);
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会自动回收无效的引用。
可见,并不是所有的优雅代码都能优雅的运行的。
更多推荐
已为社区贡献4条内容
所有评论(0)