为了账号安全,请及时绑定邮箱和手机立即绑定

HashMap、ConcurrentHashMap和SynchronizedMap – 哈希表在Java中的同步处理

标签:
Java

常见问题

  • ConcurrentHashMap和Collections.synchronizedMap(Map)分别是什么?

  • ConcurrentHashMap和Collections.synchronizedMap(Map)在性能上有什么区别?

  • ConcurrentHashMap和Collections.synchronizedMap(Map)的优劣对比?

  • 关于HashMap和ConcurrentHashMap的常见面试问题

在这篇文章中,将涵盖以上的问题,并解释我们应该如何将HashMap变得线程安全

原因

Map是一个通过键值对的形势来存储元素的容器,其中,要求key保持唯一,并且每一个key唯一对应一个value。如果你在设计一个高度并行化的程序,并且需要在不同的线程中去读取或者修改一个Map对象,那么使用线程安全的Map则是一个理想的选择。最典型的一个例子就是生产者-消费者模式,生产者不断的修改Map而消费者同时也在读取Map中的值。

那么对于Map来说,什么是线程安全呢?简单的来说,就是当多个线程同时在使用一个Map的时候,至少有一个线程对Map的结构进行了修改,那么必须保证这个修改被立即同步到其他线程中去,避免其他线程获取到错误的值。

解决方案

有两种方法可以解决HashMap的线程安全问题:
① Java的Collections库中的synchronizedMap()方法
② 使用ConcurrentHashMap

译者注:其实还有第三种方法,使用Hashtable。不过Hashtable是Java 1.1提供的旧有类,从性能上和使用上都不如其他的替代类,因此已经不推荐使用

//HashtableMap<String, String> normalMap = new Hashtable<String, String>();//synchronizedMapsynchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());//ConcurrentHashMapconcurrentHashMap = new ConcurrentHashMap<String, String>();

ConcurrentHashMap

webp

concurrentHashMap结构

  • 当你程序需要高度的并行化的时候,你应该使用ConcurrentHashMap

  • 尽管没有同步整个Map,但是它仍然是线程安全的

  • 读操作非常快,而写操作则是通过加锁完成的

  • 在对象层次上不存在锁(即不会阻塞线程)

  • 锁的粒度设置的非常好,只对哈希表的某一个key加锁

  • ConcurrentHashMap不会抛出ConcurrentModificationException,即使一个线程在遍历的同时,另一个线程尝试进行修改。

  • ConcurrentHashMap会使用多个锁

SynchronizedHashMap

  • 会同步整个对象

  • 每一次的读写操作都需要加锁

  • 对整个对象加锁会极大降低性能

  • 这相当于只允许同一时间内至多一个线程操作整个Map,而其他线程必须等待

  • 它有可能造成资源冲突(某些线程等待较长时间)

  • SynchronizedHashMap会返回Iterator,当遍历时进行修改会抛出异常

测试案例

package com.qiuzq.jz.service.impl;import java.util.Collections;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;/**
 * @program: jz
 * @Date: 2018/11/25/025 12:17
 * @author: Mr.Qiu
 * @Description:具体例子看看Collections.synchronizedMap()和ConcurrentHashMap哪个性能更高。
 */public class T2 {    public final static int THREAD_POOL_SIZE = 5;    public static Map<String, Integer> crunchifyHashTableObject = null;    public static Map<String, Integer> crunchifySynchronizedMapObject = null;    public static Map<String, Integer> crunchifyConcurrentHashMapObject = null;    public static void main(String[] args) throws Exception {        // Test with Hashtable Object
        crunchifyHashTableObject = new Hashtable<>();
        crunchifyPerformTest(crunchifyHashTableObject);        // Test with synchronizedMap Object
        crunchifySynchronizedMapObject = Collections.synchronizedMap(new HashMap<String, Integer>());
        crunchifyPerformTest(crunchifySynchronizedMapObject);        // Test with ConcurrentHashMap Object
        crunchifyConcurrentHashMapObject = new ConcurrentHashMap<>();
        crunchifyPerformTest(crunchifyConcurrentHashMapObject);

    }    public static void crunchifyPerformTest(final Map<String, Integer> crunchifyThreads) throws InterruptedException {
        System.out.println("Test started for: " + crunchifyThreads.getClass());        long averageTime = 0;        for (int i = 0; i < 5; i++) {            long startTime = System.nanoTime();
            ExecutorService crunchifyExServer = Executors.newFixedThreadPool(THREAD_POOL_SIZE);            for (int j = 0; j < THREAD_POOL_SIZE; j++) {
                crunchifyExServer.execute(new Runnable() {                    @SuppressWarnings("unused")                    @Override
                    public void run() {                        for (int i = 0; i < 500000; i++) {
                            Integer crunchifyRandomNumber = (int) Math.ceil(Math.random() * 550000);                            // Retrieve value. We are not using it anywhere
                            Integer crunchifyValue = crunchifyThreads.get(String.valueOf(crunchifyRandomNumber));                            // Put value
                            crunchifyThreads.put(String.valueOf(crunchifyRandomNumber), crunchifyRandomNumber);
                        }
                    }
                });
            }            // Make sure executor stops
            crunchifyExServer.shutdown();            // Blocks until all tasks have completed execution after a shutdown request
            crunchifyExServer.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);            long entTime = System.nanoTime();            long totalTime = (entTime - startTime) / 1000000L;
            averageTime += totalTime;
            System.out.println("2500K entried added/retrieved in " + totalTime + " ms");
        }
        System.out.println("For " + crunchifyThreads.getClass() + " the average time is " + averageTime / 5 + " ms\n");
    }

}

webp

效率对比图

结构显示,ConcurrentHashMap性能是明显优于Hashtable和SynchronizedMap的,ConcurrentHashMap花费的时间比前两个的一半还少。



作者:头脑之外
链接:https://www.jianshu.com/p/b2618cb96770


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消