1. ConcurrentHashMap的加锁原理
ConcurrentHashMap是Java并发包java.util.concurrent中提供的一个线程安全的哈希表实现。在Java 8之前,ConcurrentHashMap使用分段锁(Segment Locking)来实现并发控制,每个Segment包含一个小的哈希表,多个Segment共同组成完整的ConcurrentHashMap。每个Segment有一个独立的锁,因此最多支持16个线程并发访问不同的Segment。
然而,在Java 8及以后的版本中,ConcurrentHashMap的加锁机制进行了改进,不再使用分段锁,而是采用了以下机制:
- CAS操作:ConcurrentHashMap使用无锁的CAS操作(Compare And Swap)来更新内部的一些计数器和链表节点。CAS是一种乐观锁,它通过比较内存中的值和预期的值,如果相同则更新为新值,这个过程是原子性的。-----自旋锁
- synchronized锁:对于哈希表中的每个桶(bucket),当需要对桶中的链表或红黑树进行操作时,会使用synchronized关键字对桶进行加锁。这种锁是针对单个桶的,而不是整个哈希表,因此锁的粒度更细,减少了锁竞争,提高了并发性能。
- 自旋锁和重试机制:在插入或更新操作时,如果遇到并发冲突,ConcurrentHashMap会采用自旋锁和重试机制来处理。线程会尝试多次获取锁,而不是一遇到冲突就阻塞,这样可以减少线程切换的开销。
- 扩容机制:ConcurrentHashMap支持并发扩容,当哈希表需要扩容时,会创建一个新的数组,并逐步将旧数组中的元素迁移到新数组中。这个过程是多个线程协同完成的,迁移过程中会保持数据的完整性。
2. CopyOnWrite
CopyOnWrite容器是Java并发包java.util.concurrent中提供的一种并发容器,主要用于读多写少的场景。CopyOnWrite容器的主要特点是:在读操作时,不加锁,而在写操作时,通过复制底层数组或数据结构来避免并发修改的问题。
CopyOnWrite容器在多个写操作时的处理流程:
- 线程A想要对容器进行写操作,如添加或删除元素。
- 线程A复制当前容器中的数组或数据结构,创建一个新的副本。
- 线程A在新副本上进行修改。
- 同时,线程B也想要对容器进行写操作。
- 线程B同样复制当前容器中的数组或数据结构,创建另一个新的副本。
- 线程B在它的副本上进行修改。
- 当线程A完成修改后,将新副本的引用更新到容器中,完成写操作。
- 同样,线程B完成修改后,也将它的副本的引用更新到容器中。
在这个过程中,虽然线程A和线程B都进行了写操作,但它们的操作是隔离的,因为它们各自操作的是不同的数组副本。最后,最后一个完成写操作的线程的修改结果会成为容器的最终状态。