并发容器ConcurrentHashMap

【并发容器ConcurrentHashMap】突击并发编程JUC系列演示代码地址:
作者:故人
出处:
本节让我们一起研究一下该容器是如何在保证线程安全的同时又能保证高效的操作 。ConcurrentHashMap 是线程安全且高效的 HashMap。
为什么要使用ConcurrentHashMap在并发编程中使用 HashMap 可能导致程序死循环 。 而使用线程安全的 HashTable 效率又非常低下 , 基于以上两个原因 , 便有了 ConcurrentHashMap 的登场机会 。
线程不安全的HashMap在多线程环境下 , 使用 HashMap 进行put操作会引起死循环 , 导致CPU利用率接近100% , 所以在并发情况下不能使用 HashMap。 例如 , 执行以下代码会引起死循环 。
public class HashMapExample {// 请求总数public static int clientTotal = 5000;// 同时并发执行的线程数public static int threadTotal = 200;private static Map map = new HashMap<>(2);public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newFixedThreadPool(threadTotal);final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for (int i = 0; i < clientTotal; i++) {final int count = i;executorService.execute(() -> {try {update(count);} catch (Exception e) {e.printStackTrace();}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("size:"+ map.size());}private static void update(int i) {map.put(i, i);}}HashMap 在并发执行put操作时会引起死循环 , 是因为多线程会导致 HashMap 的 Entry 链表形成环形数据结构 , 一旦形成环形数据结构 , Entry 的 next 节点永远不为空 , 就会产生死循环获取 Entry 。
效率低下的HashTableHashTable 容器使用 synchronized 来保证线程安全 , 但在线程竞争激烈的情况下 HashTable 的效率非常低下 。 因为当一个线程访问 HashTable 的同步方法 , 其他线程也访问 HashTable 的同步方法时 , 会进入阻塞或轮询状态 。 如线程 1 使用 put 进行元素添加 , 线程2不但不能使用put方法添加元素 , 也不能使用 get 方法来获取元素 , 所以竞争越激烈效率越低 。
ConcurrentHashMap 的锁分段技术可有效提升并发访问率HashTable 容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问 HashTable 的线程都必须竞争同一把锁 , 假如容器里有多把锁 , 每一把锁用于锁容器其中一部分数据 , 那么当多线程访问容器里不同数据段的数据时 , 线程间就不会存在锁竞争 , 从而可以有效提高并发访问效率 , 这就是 ConcurrentHashMap 所使用的锁分段技术 。 首先将数据分成一段一段地存储 , 然后给每一段数据配一把锁 , 当一个线程占用锁访问其中一个段数据的时候 , 其他段的数据也能被其他线程访问 。
ConcurrentHashMapExample 示例public class ConcurrentHashMapExample {// 请求总数public static int clientTotal = 5000;// 同时并发执行的线程数public static int threadTotal = 200;private static Map map = new ConcurrentHashMap<>();public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newFixedThreadPool(threadTotal);final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for (int i = 0; i < clientTotal; i++) {final int count = i;executorService.execute(() -> {try {update(count);} catch (Exception e) {e.printStackTrace();}countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println("size:" + map.size());}private static void update(int i) {map.put(i, i);}}


推荐阅读