Java ConcurrentHashMap面试题
1. ConcurrentHashMap的工作原理是什么?
ConcurrentHashMap是线程安全的哈希表,通过高效的并发控制机制,在并发环境下进行数据存取。在JDK 1.7和JDK 1.8中有两种不同的实现。
JDK 1.7 实现:
- 分段锁(Segment):ConcurrentHashMap将整个HashMap分成多个Segment,每个Segment类似一个小的HashMap,并且用锁来保护。因此,多个线程可以同时访问不同的Segment,从而提高并发性能。
JDK 1.8 实现:
- CAS 和
synchronized
:ConcurrentHashMap移除了分段锁机制,转而使用CAS操作和synchronized
锁来实现操作原子性。 - 哈希桶数组(Node数组):每个桶存放一个链表(或红黑树,在冲突严重时转换)。
volatile
关键字:使用volatile
关键字保证变量的可见性,避免脏数据。
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "One");
String value = map.get(1);
2. ConcurrentHashMap和Hashtable的区别是什么?
- 锁的粒度:ConcurrentHashMap采用分段锁(JDK 1.7)或CAS和
synchronized
(JDK 1.8)。锁的粒度更细,允许更高的并发度。Hashtable对每个方法都加上synchronized
关键字进行同步,锁的粒度较大,导致高并发性能较差。
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "One");
String value = hashtable.get(1);
3. ConcurrentHashMap的size方法是如何实现的?
ConcurrentHashMap的size
方法用于返回哈希表中键值对的数量。由于哈希表是并发访问的,获取精确的大小可能会导致性能瓶颈,因此size
方法采用了一种折衷的方法。
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "One");
int size = map.size();
4. ConcurrentHashMap的get方法是否要加锁,为什么?
ConcurrentHashMap的get
方法不需要加锁。get
方法采用了unsafe
方法,来保证线程安全。
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "One");
String value = map.get(1);
5. ConcurrentHashMap迭代器是强一致性还是弱一致性?
ConcurrentHashMap迭代器是弱一致性的,在迭代过程中可修改集合。
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
// 可以修改map
}
6. ConcurrentHashMap 1.7和1.8的区别是什么?
- JDK 1.7:分段锁(Segment),整个HashMap被分为多个Segment,每个Segment用一把锁保护。
- JDK 1.8:移除了分段锁机制,转而使用CAS操作和
synchronized
锁来实现操作原子性,提高了并发性。
// JDK 1.7
ConcurrentHashMap<Integer, String> chm1 = new ConcurrentHashMap<>();
// JDK 1.8
ConcurrentHashMap<Integer, String> chm2 = new ConcurrentHashMap<>();
7. ConcurrentHashMap为什么不支持key或value为null?
ConcurrentHashMap不允许null key或null value,因为null检查是HashMap中查找和更新操作的一部分。如果允许null值,将需要额外的null检查,这会降低性能。
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(null, "One"); // 抛出NullPointerException
map.put(1, null); // 抛出NullPointerException
8. ConcurrentHashMap的put()方法如何实现线程安全?
在JDK 1.8中,put
方法通过CAS
操作尝试插入或更新。如果失败则使用synchronized
锁保护链表或红黑树操作。
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "One");
9. ConcurrentHashMap的扩容机制是怎样的?
当负载因子达到阈值时,进行扩容。扩容时锁住整个数组,防止其他线程的并发操作。
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 100; i++) {
map.put(i, "Value" + i); // 可能会触发扩容
}