concurrenthashmap是一个非常好的map实现,在高并发操作的场景下会有非常好的效率。实现的目的主要是为了避免同步操作时对整个map对象进行锁定从而提高并发访问能力。
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。
static final class HashEntry<K,V> { final K key; // 声明 key 为 final 型 final int hash; // 声明 hash 值为 final 型 volatile V value; // 声明 value 为 volatile 型 final HashEntry<K,V> next; // 声明 next 为 final 型 HashEntry(K key, int hash, HashEntry<K,V> next, V value) { this.key = key; this.hash = hash; this.next = next; this.value = value; } }
static final class Segment<K,V> extends ReentrantLock implements Serializable { transient volatile int count; //在本 segment 范围内,包含的 HashEntry 元素的个数 //volatile 型 transient int modCount; //table 被更新的次数 transient int threshold; //默认容量 final float loadFactor; //装载因子 /** * table 是由 HashEntry 对象组成的数组 * 如果散列时发生碰撞,碰撞的 HashEntry 对象就以链表的形式链接成一个链表 * table 数组的数组成员代表散列映射表的一个桶 */ transient volatile HashEntry<K,V>[] table; /** * 根据 key 的散列值,找到 table 中对应的那个桶(table 数组的某个数组成员) * 把散列值与 table 数组长度减 1 的值相“与”,得到散列值对应的 table 数组的下标 * 然后返回 table 数组中此下标对应的 HashEntry 元素 * 即这个段中链表的第一个元素 */ HashEntry<K,V> getFirst(int hash) { HashEntry<K,V>[] tab = table; return tab[hash & (tab.length - 1)]; } Segment(int initialCapacity, float lf) { loadFactor = lf; setTable(HashEntry.<K,V>newArray(initialCapacity)); } /** * 设置 table 引用到这个新生成的 HashEntry 数组 * 只能在持有锁或构造函数中调用本方法 */ void setTable(HashEntry<K,V>[] newTable) { threshold = (int)(newTable.length * loadFactor); table = newTable; } }
注意Segment继承了ReentrantLock 锁
get操作不需要锁。第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。对hash链进行遍历不需要加锁的原因在于链指针next是final的。但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在table数组中的值。这使得getFirst(hash)可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。
- V get(Object key, int hash) {
- if (count != 0) { // read-volatile
- HashEntry e = getFirst(hash);
- while (e != null) {
- if (e.hash == hash && key.equals(e.key)) {
- V v = e.value;
- if (v != null)
- return v;
- return readValueUnderLock(e); // recheck
- }
- e = e.next;
- }
- }
- return null;
- }
- V readValueUnderLock(HashEntry e) {
- lock();
- try {
- return e.value;
- } finally {
- unlock();
- }
- }
put操作一上来就锁定了整个segment,这当然是为了并发的安全,修改数据是不能并发进行的,必须得有个判断是否超限的语句以确保容量不足时能够rehash,而比较难懂的是这句int index = hash & (tab.length - 1),原来segment里面才是真正的hashtable,即每个segment是一个传统意义上的hashtable,如上图,从两者的结构就可以看出区别,这里就是找出需要的entry在table的哪一个位置,之后得到的entry就是这个链的第一个节点,如果e!=null,说明找到了,这是就要替换节点的值(onlyIfAbsent == false),否则,我们需要new一个entry,它的后继是first,而让tab[index]指向它,什么意思呢?实际上就是将这个新entry插入到链头,剩下的就非常容易理解了。
- V put(K key, int hash, V value, boolean onlyIfAbsent) {
- lock();
- try {
- int c = count;
- if (c++ > threshold) // ensure capacity
- rehash();
- HashEntry[] tab = table;
- int index = hash & (tab.length - 1);
- HashEntry first = (HashEntry) tab[index];
- HashEntry e = first;
- while (e != null && (e.hash != hash || !key.equals(e.key)))
- e = e.next;
- V oldValue;
- if (e != null) {
- oldValue = e.value;
- if (!onlyIfAbsent)
- e.value = value;
- }
- else {
- oldValue = null;
- ++modCount;
- tab[index] = new HashEntry(key, hash, first, value);
- count = c; // write-volatile
- }
- return oldValue;
- } finally {
- unlock();
- }
- }
remove操作非常类似put,但要注意一点区别,中间那个for循环是做什么用的呢?(*号标记)从代码来看,就是将定位之后的所有entry克隆并拼回前面去,但有必要吗?每次删除一个元素就要将那之前的元素克隆一遍?这点其实是由entry 的不变性来决定的,仔细观察entry定义,发现除了value,其他所有属性都是用final来修饰的,这意味着在第一次设置了next域之后便不能再 改变它,取而代之的是将它之前的节点全都克隆一次。至于entry为什么要设置为不变性,这跟不变性的访问不需要同步从而节省时间有关,关于不变性的更多 内容,请参阅之前的文章《线程高级---线程的一些编程技巧》
- V remove(Object key, int hash, Object value) {
- lock();
- try {
- int c = count - 1;
- HashEntry[] tab = table;
- int index = hash & (tab.length - 1);
- HashEntry first = (HashEntry)tab[index];
- HashEntry e = first;
- while (e != null && (e.hash != hash || !key.equals(e.key)))
- e = e.next;
- V oldValue = null;
- if (e != null) {
- V v = e.value;
- if (value == null || value.equals(v)) {
- oldValue = v;
- // All entries following removed node can stay
- // in list, but all preceding ones need to be
- // cloned.
- ++modCount;
- HashEntry newFirst = e.next;
- * for (HashEntry p = first; p != e; p = p.next)
- * newFirst = new HashEntry(p.key, p.hash,
- newFirst, p.value);
- tab[index] = newFirst;
- count = c; // write-volatile
- }
- }
- return oldValue;
- } finally {
- unlock();
- }
- }
探索 ConcurrentHashMap 高并发性的实现机制:
http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
ConcurrentHashMap之实现细节
http://www.iteye.com/topic/344876
Map的并发处理(ConcurrentHashMap)
http://zl198751.iteye.com/blog/907927
集合框架 Map篇(4)----ConcurrentHashMap
http://hi.baidu.com/yao1111yao/blog/item/232f2dfc55fbcd5ad7887d9f.html
java ConcurrentHashMap中的一点点迷惑
http://icanfly.iteye.com/blog/1450165
相关推荐
相比HashTable,ConcurrentHashMap通过采用锁分离技术和更细粒度的锁定策略来提升性能。HashTable使用全局同步锁,即在读写操作时都需要对整个哈希表加锁,这会导致在多线程环境下性能瓶颈。 ConcurrentHashMap的...
通过以上分析可以看出,`ConcurrentHashMap`的设计非常巧妙,它利用锁分离技术和不可变性来实现高效的并发访问。这种设计不仅提高了多线程环境下的性能,同时也保持了良好的线程安全性。对于需要在多线程环境下高效...
4. **锁分离** 根据读写操作特性,使用读锁和写锁,读锁允许多个读取,写锁是独占的。LinkedBlockingQueue的实现就利用了这种分离,使得take()和put()操作可以并发执行。 5. **重入锁和内部锁** 重入锁...
每个段内部采用了类似于`HashMap`的结构,但通过锁分离技术实现并发控制。 2. **非阻塞算法**:`ConcurrentHashMap`使用了CAS(Compare and Swap)操作来实现无锁编程。CAS是一种原子操作,可以在不使用锁的情况下...
为了减少这些开销并提高程序的执行效率,我们可以采用降低锁粒度的技术。 首先,理解上下文切换。当一个线程被阻塞,操作系统需要保存当前线程的状态,并恢复另一个线程的执行状态,这就是上下文切换。每次上下文...
此外,使用锁分离技术,如ConcurrentHashMap,可以将数据分散到不同的缓存行中,降低锁竞争,提高并发性能。 5. **缓存行填充**: 缓存行填充是为了解决CPU缓存一致性问题,如False Sharing。某些处理器有固定的...
- **并发编程**:线程同步、锁机制、并发容器如ConcurrentHashMap、CopyOnWriteArrayList等。 - **IO与NIO**:文件操作、流的使用、非阻塞I/O模型。 - **反射与注解**:动态类型、运行时类型检查、自定义注解及其...
- **锁分离**:讲解锁分离技术,以及如何通过减少锁的竞争来提高程序性能。 - **无锁编程**:探讨无锁编程的基本原理和技术细节,包括使用原子变量进行并发控制的方法。 - **并发集合**:详细介绍Java提供的各种并发...
- **锁分离**:如ConcurrentHashMap,使用分段锁技术,减少锁竞争,提高并发性。 - **Copy-On-Write(写时复制)**:如CopyOnWriteArrayList,写操作时复制整个容器,避免修改时的同步开销。 - **无锁数据结构**...
以下是一些提高 Java 中锁性能的技术和策略: 1. **分离锁(Lock Segregation)**: 分离锁是指将原本由一个锁保护的数据结构分解成多个更小的独立部分,每个部分都有自己的锁。这样可以减少锁的竞争,提高并发性...
5. **并发集合**:Java并发包中的并发集合类如ConcurrentHashMap、CopyOnWriteArrayList等,它们在内部使用了锁分段技术和弱一致性等机制,提高了在并发环境下的性能和安全性。 在实际开发中,合理使用这些并发工具...
26. ConcurrentHashmap的锁机制及其性能考量。 27. MySQL存储引擎MyISAM和InnoDB的区别。 28. Memcache与Redis的对比。 29. MySQL的行级锁和性能优化方法。 30. Linux系统日志查看方法和网络进程的查看。 31. 二进制...
6. **ConcurrentHashMap实现**:ConcurrentHashMap是Java中的并发集合,基于分段锁机制实现高并发下的高效操作。 7. **实时计算与数仓** - 实时计算场景:如实时报警、实时报表、实时业务监控等,需要高实时性来...
1. **高并发处理**:秒杀场景下,系统需在短时间内处理成千上万的请求,因此需要掌握Java并发编程,如线程池(ThreadPoolExecutor)、并发集合(ConcurrentHashMap、CopyOnWriteArrayList)以及锁机制(synchronized...
- **并发Map**:`ConcurrentHashMap`利用锁分离技术提高并发性能,读操作无锁。 - **并发Queue**:`ConcurrentLinkedQueue`使用无锁算法实现,适用于高并发场景。`BlockingQueue`则用于生产者-消费者模式,当队列满...
ConcurrentHashMap 是 Java 中的一个线程安全的哈希表实现,使用锁分段技术来实现线程安全。 二、高并发相关 高并发相关知识点包括 Java 线程池原理、CAS 机制、Synchronized 实现原理、ReentrantLock 实现原理等...
3. **使用`java.util.concurrent`提供的并发集合类**:例如`ConcurrentHashMap`等,这些类内部实现了锁分离技术,可以支持多个线程同时访问不同的数据项而不发生冲突,从而提高并发性能。 ### ORM规则与阿里巴巴...
并发集合如ConcurrentHashMap、ConcurrentLinkedQueue等,为多线程环境下的数据共享提供了高效且线程安全的解决方案。 在Java开发中,IO流、网络编程、反射和序列化也是关键知识点。IO流处理输入输出操作,分为字节...