「这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战
如果在面试中,被面试官问道这样的面试问题,我们应该能轻易地回答,这道面试是一道比较基础的题。在一些初中级别的面试的时候,经常被问到,它考核的点比较全面,包含内容也比较多。
HashMap 不是现在安全的,往往在写程序的时候需要通过一些方法来进行回避。其实 JDK 原生就提供了两种方式来让 HashMap 支持线程安全。在用的时候可以自己进行随机选择,你可以选择其中的任意一种。
第一种方式是通过 Collections.synchronizedMap() 方法返回一个新的 Map 结构,这个新的 Map 就是线程安全的。这样要求我们习惯基于接口编程,因为返回的并不是 HashMap,而是一个 Map 的实现。什么意思?看起来虽然返回的依然是 HashMap,但是注意了,它是另外的子类实现,不是我们原来的 HashMap。这种方式在用的时候日常工作中用的也比较多。这个方法在进行实现的时候加了一个 synchronized 关键字,所以它就本身表示了是一种线程安全的处理方式。
第二种方法重新改写 HashMap。具体可以看 java.util.concurrent 中的 ConcurrentHashMap 类。这个方法比方法一有了更大的一些改进。什么意思?当你加上 concurrent 之后,意味着它是一个支持并发的 HashMap 的结构。注意,如果你对 Java 了解比较多的话,这个包你应该比较熟悉,它里面提供了一系列支持并发编程的一些子类实现,让我们在进行扩展的,能不会出现数据安全问题。
接下来我们看一下方法一和方法二的特点,在面试中被问到这个问题的时候,你可以简单地回答这两个方式,没问题。但是为了凸显你个人技术的一个全面性,以及为了彰显你个人的技术优势,最好把下面的特点也说一下。
方法一的特点: 通过 Collections.synchronizedMap() 来装所有不安全的 HashMap 的方法,就连 toString、HashCode 都进行了封装。封装的关键点有两处,第一,使用了经典的 synchronized 关键字来进行互斥同步。第二,使用了代理模式 new 一个新的类,这个类同样实现了 Map 接口,在 HashMap 上面,synchronized 关键字锁住的是对象,所以第一个申请的得到所其他线程将进入到阻塞状态,等待唤醒。也就是多线程处理的时候,如果我一个线程已经抢占到当前 HashMap 的这个访问资源了,那么其他线程你就只能去进行等待。优点是什么呢?代码实现,非常简单,一看就懂。缺点是从锁的角度来看,方法一使用了锁住方法,基本上是锁住了尽可能大代码块性能上面可能会有所损耗。
方法二的特点:重新编写了 HashMap 的结构,比较大的改变有以下几点:比如使用了新的锁机制,把 HashMap 进行了拆分,拆分成了多个独立的块,这样在并发的情况下减少了所冲突的可能性,使用 NonfairSync。这个特性调用了 CAS 指令来确保原子性和互质性。当如果有多个线程恰好操作到同一个c segment 的上面的时候,那么只会有一个线程得到运行。优点:需要互斥的代码段比较少,性能会比较好一点。而 ConcurrentHash 把整个 Map 切分成了很多个块,发生碰撞的几率大大降低,性能会比较好。缺点:代码繁琐。当然这只是 JDK1.7 版本里面的实现方式,在 JDK1.8 之后也给取消掉了。