前言 
  之前的文章中有介绍过关于线程安全的集合类使用性能对比,线程安全集合的几种方式、性能对比、适用场景分析,本文再来看看如何利用ConcurrentHashMap的特性,使其在多线程开发中做到既安全又高效。
   代码示例 
import com.google.common.base.Stopwatch;import com.google.common.collect.Maps;import junit.framework.Assert;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.Map;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.ThreadLocalRandom;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import java.util.stream.IntStream;public class ConcurrentHashMapTest {    private final static Logger log = LoggerFactory.getLogger(ConcurrentHashMapTest.class);    // 并行线程数    private static final int THREAD_POOL = 8;    // 一共要执行的次数    private static final int LOOP_TIME = 10000000;    // 元素的范围0~9    private static final int RANDOM_RANG = 10;    public static void main(String[] args) throws InterruptedException {        benchmark();    }        private static void benchmark() throws InterruptedException {        Stopwatch stopwatch = Stopwatch.createStarted();        Map normalMap = normalVersion();        stopwatch.stop();        // 测试验证,每次循环加1,最终所有key对应的value值加起来应该等于LOOP_TIME        Assert.assertEquals(LOOP_TIME, normalMap.values().stream().mapToInt(i -> i).reduce(0, Integer::sum));        log.info("normal elapsed -> {}", stopwatch);        stopwatch.reset();        stopwatch.start();        Map optimizeMap = optimizeVersion();        stopwatch.stop();        // 测试验证,每次循环加1,最终所有key对应的value值加起来应该等于LOOP_TIME        Assert.assertEquals(LOOP_TIME, optimizeMap.values().stream().mapToInt(AtomicInteger::intValue).reduce(0, Integer::sum));        log.info("optimize elapsed -> {}", stopwatch);    }        private static Map normalVersion() throws InterruptedException {        Map normalMap = Maps.newHashMap();        ForkJoinPool forkJoinPool = new ForkJoinPool(THREAD_POOL);        forkJoinPool.execute(() -> IntStream.rangeClosed(1, LOOP_TIME).parallel().forEach(i -> {            String key = "item_" + ThreadLocalRandom.current().nextInt(RANDOM_RANG);            synchronized (normalMap) {                normalMap.put(key, normalMap.getOrDefault(key, 0) + 1);            }        }));        forkJoinPool.shutdown();        forkJoinPool.awaitTermination(1, TimeUnit.MINUTES);        return normalMap;    }        private static Map optimizeVersion() throws InterruptedException {        Map optimizeMap = Maps.newConcurrentMap();        ForkJoinPool forkJoinPool = new ForkJoinPool(THREAD_POOL);        forkJoinPool.execute(() -> IntStream.rangeClosed(1, LOOP_TIME).parallel().forEach(i -> {            String key = "item_" + ThreadLocalRandom.current().nextInt(RANDOM_RANG);            optimizeMap.computeIfAbsent(key, v -> new AtomicInteger()).getAndIncrement();        }));        forkJoinPool.shutdown();        forkJoinPool.awaitTermination(1, TimeUnit.MINUTES);        return optimizeMap;    }}
   执行结果,大约5倍的差距
    实现原理 
  从computeIfAbsent源码可以大致了解,虽然也用到了synchronized,但是实际上只有锁了一个Node,并不会影响到其他Node的使用。
      最后再结合AtomicInteger来完成计数功能,unsafe相关方法可以直接操作内存地址,利用cas方式来保证线程安全。
      可以看出为了解决多线程资源共享,除了最简单的直接在方法层面使用synchionzed之外,还可以思考如何尽量减少锁的范围,比如ConcurrentHashMap中为每一个链表准备一把锁,这样多个链表之间就不会互相影响,也可以使用cas的方式来保证即使真的发生冲突时也不会有问题。