ConcurrentHashMap是如何提高并发时的吞吐性能(一)
guibin.beijing@gmail.com
为并发吞吐性能所做的优化
ConcurrentHashMap使用了一些技巧来获取高的并发性能,同时避免了锁。这些技巧包括:
- 为不同的Hash bucket(所谓hash bucket即不同范围的key的hash值)使用多个写锁;
- 利用JMM(Java Memory Model,java内存模型)的不确定性使得持有锁的时间最小化,或者从根本上避免使用锁。
ConcurrentHashMap为最常用的场景进行了优化,比如获取一个已经存在于Map中的值。事实上,绝大多数成功的get()操作在运行中根本就没有使用锁,这是因为利用了JMM的不确定性。
多个写锁
回忆一下HashTable的线程安全是因为使用了一个单独的全部Map范围的锁,这个锁在所有的插入、删除、查询操作中都会持有,甚至在使用Iterator遍历整个Map时也会持有这个单独的锁。当锁被一个线程持有时,就能够防止其他线程访问该Map,即便其他线程都处于闲置状态。这种单个锁的机制极大的限制了并发的性能。
而ConcurrentHashMap抛弃了仅仅使用整个Map范围的一个锁的机制,取而代之的是使用了32个锁,每个锁会负责hash bucket的一个子集(即负责一部分key的hash值范围)。而且这些锁仅仅被那些更改Map内容的操作使用,比如put(), remove()。拥有32个单独的锁意味时最多可以有32个线程同时修改Map,这并不是说如果有少于32个线程同时修改Map而没有线程被阻塞,32仅仅是理论上的并发写操作的极限,在实际中一般不一定会达到。在目前的软硬件条件下,对多数程序而言,32个锁总比一个锁要好。
Map范围的锁
32个独立的锁,每个锁负责hash bucket的一个子集,这意味着如果有些排他的访问操作,就需要获得全部的32个锁,比如rehashing(即扩充hash bucket的数量,当HashMap的key增长时重新分布key元素)就必须是排他的访问。但是JAVA语言本身没有提供一种简单的方式来获取变长的锁,由于这种操作不会频繁发生,那么ConcurrentHashMap是使用递归实现Map范围的锁。
JMM(Java内存模型)概览
JMM就是Java内存模型,它定义了Java的线程之间如何通过内存进行交互。简单说就是: JVM中存在一个主内存(Main Memory or Java Heap Memory),JAVA中所有变量都储存在主存中,对于所有线程都是共享的。每个线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。工作内存里的变量, 在多核CPU的情况下, 将大部分储存于处理器高速缓存中, 高速缓存在不经过内存时, 所以线程之间也是不可见的。详细的关于JMM的介绍会在后面的文章中。
大家都知道,CPU尽量使用自己的寄存器内部的数据,这样会极大的提高性能。JAVA规范(JLS-Java Language Specification)中规定了一些内存操作不必立即被其他线程发现,并且还提供了两个语言层面的机制来确保在多个线程之间保持内存操作的一致性:synchronized和volatile。根据JSL中的描述"In the absence of explicit synchronization, an implementation is free to update the main memory in an order that may be surprising." (在没有显示的同步时,一个操作可以以一种令人惊讶的方式自由的更新主存。)这意思是说:没有同步时,在一个线程中写操作的操作顺序也许和另外一个线程的写操作顺序不一样,并且更新内存变量会在不确定的时间之后被其他线程发现。
而使用synchronized的最根本的原因就是确保线程访问关键代码段的原子性。synchronized实际上提供了三个功能atomicity, visibility, and ordering(原子性,可见性和顺序行)。所谓原子性很好理解也很直接,就是确保不同线程再次进入同一区域的互斥性,防止在同一时刻有多于一个线程可以访问到被保护的代码段。很不幸的是许多的文章只强调了synchronized的原子性方面,而不说其他两个方面。在JMM中,同步扮演了一个重要的角色,当获取和释放monitor(锁)的时候,同步操作使得JVM执行了内存屏障(execute memory barriers)。
当一个线程获取一个锁时,它便执行了一次内存读屏障(read barriers)——所谓内存屏障就是使得任何其他线程缓存的本地内存(CPU内部缓存或者CPU寄存器)无效,然后使得其他所有线程所在的CPU重新从主存(内存)读取这些变量的值。同样,在释放锁的时候,所有其他线程均执行了一次内存写屏障(write barriers)——Flush任何已经更改的变量到主存(内存)。互斥和内存屏障的结合意味着只要程序遵循正确的同步规则(这个同步规则就是:同步任何被写的变量会下次被另一个线程正确的读;或者是同步任何读一个下一次会被另一个线程正确的更改变量),每个线程都会看到它所使用的共享变量的正确值。
如果在访问共享变量时没有同步,你会碰到一些奇怪的事情,一些改变会很快的反应到其他线程里,而其他一些更改则需要花费一些时间才能反应到其他线程中。这样的结果就会使得你如果不用synchronized,那么你不能确保你看到一致的内存视图(即相关变量在不同的线程中的值会不一致,也许有一些值是脏数据)。通常的方法,也是推荐的方法去避免这些脏数据当然是正确采用synchronized。在这种情况下,比如在广泛使用的基础类ConcurrentHashMap中就值得使用一些额外的专门知识和功夫去开发,以获得高性能。
参考资料:
http://www.ibm.com/developerworks/java/library/j-jtp08223/
http://www.cs.umd.edu/~pugh/java/memoryModel/
分享到:
相关推荐
并发容器的出现解决了传统同步容器在性能上的不足,通过引入更先进的并发控制策略,如锁分段、CAS算法等,提升了程序的并发性和吞吐量。 首先,我们要理解什么是同步容器。Java 集合框架中的非线程安全容器,如 ...
这些集合在设计时考虑了多线程环境下的性能和安全性,学习它们的原理和使用方法对于提高并发程序的效率至关重要。 5. **并发编程模式**:书中可能会涵盖一些经典的并发编程模式,如生产者消费者模型、工作窃取和...
这表明ConcurrentHashMap在设计时考虑了内存模型对并发控制的影响,并且通过这种方式来进一步提高性能。 5. 适应新的Java内存模型(JMM):随着Java内存模型的更新,Doug Lea的util.concurrent包中的...
这些概念是现代软件开发中不可或缺的一部分,尤其是在构建高性能、高可用性系统时尤为重要。 ### 并发与多线程 1. **并发**: - 定义:指的是多个事件在同一时间段内发生。 - 实现方式:在计算机系统中,可以...
- 除了互斥锁,还有读写锁(`ReentrantReadWriteLock`),允许多个线程同时读取但限制同时写入,提高了并发性能。 6. **其他并发工具** - `java.util.concurrent`包提供了一系列的并发工具类,如`Semaphore`...
多线程与高并发是计算机科学中非常重要的两个概念,它们在提高软件程序的性能、响应速度和资源利用率方面起着至关重要的作用。在当今的互联网时代,特别是在产业互联网和5G技术的推动下,多线程和高并发的应用变得...
在IT行业中,特别是后端开发领域,并发编程是不可或缺的一部分,它涉及到如何高效地利用多核处理器资源,提高系统的响应速度和吞吐量。本文将根据提供的文件名称,结合并发编程的重要概念,深入探讨相关知识点。 ...
- **优势**:并发能提高系统资源利用率,提升程序响应速度,特别是在多核环境下,可以显著提高性能。 - **风险**:并发可能导致数据不一致、竞态条件、死锁等问题,需要谨慎设计并发控制策略。 6. **高并发处理的...
- **并发集合**:如ConcurrentHashMap、CopyOnWriteArrayList等,这些集合类针对多线程环境进行了优化,能够在保证线程安全的同时提高性能。 - **线程通信**:包括线程间的同步、等待、通知等机制,是实现复杂并发...
在IT行业中,并发编程是提升系统性能和效率的关键技术,特别是在多核处理器和高并发应用场景中。本项目“并发编程demo”聚焦于Java平台上的并发处理,主要利用了`java.util.concurrent`包中的工具和机制。这个包包含...
Java并发库提供了一些高级工具,如并发容器(ConcurrentHashMap、CopyOnWriteArrayList等)、并发工具类(CountDownLatch、CyclicBarrier、Semaphore)以及Fork/Join框架,这些都能帮助我们编写出高性能的并发代码。...
其中,`CopyOnWriteArrayList`和`CopyOnWriteArraySet`是典型的写时复制(Copy-On-Write,COW)容器,它们在写操作时创建容器副本,确保读操作不会被阻塞,从而提高并发性能。当线程尝试修改容器时,它会先复制一份...
并发编程是Java高级编程技能中的重要组成部分,尤其是在需要处理大量数据、提供快速响应、实现高吞吐量和高可伸缩性软件时显得尤为重要。 在Java并发编程中,多线程编程是指同时运行多个线程(Thread),每个线程...
Java并发编程是Java开发中的重要领域,它涉及到多线程、同步、锁机制、线程池等关键概念,是提高程序性能和效率的关键技术。在Java中,并发编程的运用可以充分利用多核处理器的能力,实现高效的多任务处理。以下是对...
并发编程是指在一个时间段内同时执行多个任务或进程,它利用了处理器的多核或多CPU资源,提高了系统的吞吐量和响应性。Java作为一款广泛应用于企业级应用的语言,提供了丰富的并发工具和API,如线程、同步机制、并发...
在Java世界中,开发高性能、高并发Web应用是许多企业和开发者关注的重点。为了实现这一目标,需要深入理解Java平台的特性以及相关技术。本篇内容将围绕如何利用Java进行高效Web应用开发展开,主要涉及以下几个关键...
并发容器类ConcurrentHashMap和CopyOnWriteArrayList等,采用细粒度的锁和弱一致性来减少锁的竞争,提高并发访问效率。 线程池是管理线程生命周期的重要组件,例如java.util.concurrent中的ThreadPoolExecutor,它...
- NIO提供了一种新的I/O模型,允许线程在等待I/O操作完成时不被阻塞,提高了系统的吞吐量。NIO的核心组件包括选择器(Selector)、通道(Channel)和缓冲区(Buffer)。 5. 并发包JUC(java.util.concurrent): ...
在Java编程领域,高并发程序设计是一项至关重要的技能,尤其对于构建大规模、高性能的应用系统而言。本课程“实战Java高并发程序设计”旨在深入探讨如何有效地处理和优化Java应用程序中的多线程和并发问题。 首先,...
- **数据库连接池管理**:如C3P0、HikariCP等,提高数据库并发性能。 - **微服务架构下的并发设计**:在微服务场景下,如何设计和优化并发策略。 以上知识点涵盖了Java高并发系统设计的核心内容,理解和掌握这些...