ConcurrentHashMap原理面试13问

1.上次聊到HashMap在多线程环境下存在线程安全问题,一般怎么处理这种情况呢?

这时候一般使用线程安全的集合HashTable和ConcurrentHashMap。

2.讲讲HashTable和HashMap的区别?

  • HashMap相比HashTable是线程安全的,适合在多线程的情况下使用,但是效率不太乐观。因为HashTable在对所有的数据操作都会加上锁,所以效率低下。

  • 另外HashTable不允许键或者值为null,HashMap的键值都可以为null。因为HashTable用的是安全失败机制(Fail-safe)

  • 初始化容量不同。HashMap初始容量是16,HashTable是11,两者负载因子默认都是0.75

  • 迭代器不同:HashMap中Iterator迭代器是fail-fast的,而HashTable的Enumerator不是fail-fast的。

3.fail-fast fail-safe是什么

fail-fast是Java集合中的一种机制,如果遍历过程中对集合对象内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception.
原理是因为在遍历期间会使用一个modCount变量,在迭代器遍历下一个元素时,会将modCount和期望值比较,如果不等。就会抛出异常,终止遍历

java.util下的集合类都是快速失败的;java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

原理是因为采用安全机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有的集合内容,在拷贝的集合上进行遍历。所以不会抛出ConcurrentModificationException异常

4.聊一聊ConcurrentHashMap的数据结构,为什么它的并发度高?

jdk1.7中,ConcurrentHashMap和HashMap一样都是数组+链表组成的。

segment是ConcurrentHashMap的一个内部类,主要组成如下:

HashEntry跟HashMap差不多的,但是不同点是,他使用volatile去修饰了他的数据Value还有下一个节点next。

5.Volatile的特性是什么?

  1. 可见性:保证了不同线程对这个遍历操作时的可见性。即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2. 有序性:禁止指令重排
  3. 原子性:Volatile只能保证对单次读/写的原子性,不能保证i++的原子性。

6.ConcurrentHashMap并发度高的原因?

原理上来说,是因为ConcurrentHashMap采用了分段锁技术,其中Segment继承与ReentrantLock。

不会像HashTable那样不管是put和get操作都做同步处理,理论上ConcurrentHashMap支持CurrencyLevel(Segment数组数量)的线程并发。

每当一个线程独占一个Segment时,不会影响到其它Segment。

jdk1.7的put和get
put时首先会尝试获取锁,如果获取失败肯定存在其它线程竞争。1.自旋获取锁 2.重试次数达到最大重试次数后,改为阻塞锁获取。
get时,通过key的hash定位到具体的Segment,再通过一次Hash定位到具体的元素上。HashEntry中的Value都是由volatile关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。get整个过程不需要进加锁

7.ConcurrentHashMap在jdk1.7和1.8有什么区别吗?

因为还是使用数组加链表的方式,在我们去查询的时候,还得遍历链表,导致效率很低。这个和jdk1.7的HashMap是一样的问题,所以在jdk1.8中同样采用数组+链表+红黑树进行优化。

其中1.8中抛弃了原来的Segment分段锁,采用了CAS+Synchronized来保证并发安全性。

跟HashMap很像,把HashEntry改成了Node,但是作用不变。把值和next采用了volatile修饰,保证了可见性。

8.JDK1.8的存取操作流程?以及如何保证线程安全的?

jdk1.8的put

  1. 根据key值计算出hashCode
  2. 判断是否需要初始化
  3. 为当前的key定位出Node,如果为空可以写入数据,利用CAS尝试写入,失败则自旋保证成功。
  4. 判断当前是否需要扩容
  5. 如果都不满足,则利用Synchronized锁写入数据
  6. 如果数量大于8,转为红黑树结构
    jdk1.8的get
  • 根据key计算出hashcode寻址,如果在桶上那么直接返回值。
  • 如果是红黑树那就按照树的方式获取值
  • 如果不满足就按照链表的方式遍历获取值

9.上面提到的CAS是什么?自旋是什么?

CAS(Compare and Swap)是乐观锁的一种实现方式,是一种轻量级锁,JUC很多工具类的实现就是基于CAS。

线程在读取数据的时候不进行加锁,当准备写回数据的时候,会比较原值和当前值,观察原值是否有被其它线程修改,如果已被修改,则重新执行读取流程。

这种一种乐观策略,认为并发操作并不总会发生。

10.CAS就一定能保证数据没被别的线程修改过吗?

不能,比如经常的ABA问题,CAS就无法判断了。

11.什么是ABA

就是说一个线程把原值A改成了B,又来了一个线程把值又改回了A,对于这个时候判断线程,发现值还是A,它可能会误认为没有被人改过。这就是ABA问题

12.怎么解决ABA问题

在一些只追求结果的场景,ABA发生了不影响业务。但是某些情况下,比如银行的资金变动,需要记录每一笔的动向。

  • 加版本号,查询值的时候带一个版本号,判断时候连值和版本号一起判断。成功后给版本号加1
  • 加时间戳

13.CAS性能很高,但是为什么jdk1.8之后还是会用Synchronized?

Synchronized之前一直是重量级锁,但是后来Sun公司对其进行了升级。

针对Synchronized获取锁的方式,JVM使用了锁升级的优化方式,就是先试用偏向锁优先使用同一线程然后再次获取锁,如果失败,就升级为CAS轻量级锁,如果失败会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁

所以是一步步升级上去的,最初也是通过轻量级的方式锁定的。

以下是一些可能被到的concurrentHashmap面试题: 1. 什么是ConcurrentHashMapConcurrentHashMap是Java中的一个线程安全的哈希表。它是由多个分离的桶组成的,每个桶都可以被独立地锁住,从而使得多个线程可以同时访不同的桶,从而提高了并发性能。 2. ConcurrentHashMapHashMap有什么区别? ConcurrentHashMap是线程安全的,而HashMap是非线程安全的。ConcurrentHashMap内部使用了分离的桶和锁机制来保证线程安全性,并且多个线程可以同时进行读取操作,而HashMap则没有这样的机制。 3. ConcurrentHashMap的实现原理是什么? ConcurrentHashMap内部使用了分离的桶和锁机制来保证线程安全性。每个桶都有一个独立的锁,多个线程可以同时访不同的桶,从而提高了并发性能。同时,ConcurrentHashMap还使用了一种被称为“分段锁”的机制,即将整个哈希表分为多个段,每个段都有一个独立的锁来保证线程安全。 4. ConcurrentHashMap如何保证线程安全性? ConcurrentHashMap使用了分离的桶和锁机制来保证线程安全性。每个桶都有一个独立的锁,多个线程可以同时访不同的桶,从而提高了并发性能。同时,ConcurrentHashMap还使用了一种被称为“分段锁”的机制,即将整个哈希表分为多个段,每个段都有一个独立的锁来保证线程安全。 5. ConcurrentHashMap的性能如何? ConcurrentHashMap的性能非常好,尤其是在高并发环境下。由于它使用了分离的桶和锁机制,可以同时进行读取操作,从而大幅度提高了读取性能。同时,它还使用了分段锁机制来保证线程安全性,这也进一步提高了并发性能。 6. ConcurrentHashMap的缺点是什么? ConcurrentHashMap的主要缺点是占用内存较大,因为它需要维护多个分离的桶和锁。此外,由于它使用了分段锁机制,所以在进行写操作时可能会出现锁竞争,导致性能下降。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值