从volatile解读ConcurrentHashMap(jdk1.6.0)无锁读

简介:

volatile常常用于修饰多线程共享变量,用来保证该变量的可见性。volatile的语意:某个写线程对volatile变量的写入马上可以被后续的某个读线程“看”到。

volatile保证可见性的原理:volatile是通过在编译器生成字节码时,在对volatile变量进行读写指令序列的前后加入内存屏障,来禁止一些处理器重排序保证写入一定发生在读之前的这种happen-before关系。

 

简单理解:在本次线程内,当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个线程本地内存中;以后再取变量值时,就直接从本地内存中取值;当变量值在本线程里改变时,会同时把变量的新值copy到本地内存中,以便保持一致;在某个特定的时候,将本地内存的更改写到系统主内存中去;当变量在因别的线程等而改变了值,并且该变化没有写到系统主内存,本次线程的本地内存中的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致;但是当变量被volatile修饰后,每次更改该变量的时候会将更改结果写到系统主内存中,利用多处理器的缓存一致性,其他处理器会发现自己的缓存行对应的内存地址被修改,就会将自己处理器的缓存行设置为失效,并强制从系统主内存获取最新的数据。这样就能保证即使在别的线程中改变了该变量的值,在本线程中也能取到最新更改后的值。 ConcurrentHashMap之所以有较好的并发性是因为ConcurrentHashMap是无锁读和加锁写,并且利用了分段锁(不是在所有的entry上加锁,而是在一部分entry上加锁)。

那ConcurrentHashMap是怎么实现无锁读的呢?

这是在jdk1.6.0中的读的实现。


    不得不赞叹,作者深厚的功底啊,当执行读的时候,先判断count,count就是一个Segment(充当锁的角色)所守护HashEntry的数量。

 
    这里的count是被volatile修饰的。当对这段表的结构进行更改时,在退出前都会去更改count。由于volatile的语意:某个写线程对volatile变量的写入马上可以被后续的某个读线程“看”到,所以这里对count的读一定发生在对count写之后,获得是最新的count。在无锁读的方法中,首先去读取这个最近的count,保证了在执行无锁读的时候表的结构没有被改变。(利用了volatile变量写读的happen-before关系)。

   同时当把value设置为volatile时,其他线程所做的改变就能马上被当前线程感知。这样就能支持多个线程并发读了~

   不过我们也知道volatile并不能保证线程安全,它是轻量级的synchronized。

   要使 volatile变量提供理想的线程安全,必须同时满足下面两个条件:
              ● 对变量的写操作不依赖于当前值。
              ● 该变量没有包含在具有其他变量的不变式中。
举例:线程安全计数器的自增操作,其实是由3个操作读取-修改-写入操作序列组成的组合操作,volatile不能保证原子性,不能保证在操作期间该变量的值不会改变。
   其实这是一种常见的volatile的利用场景——开销较低的读-写锁策略。如果读操作远远超过写操作,您可以结合使用内部锁和 volatile变量来减少公共代码路径的开销。这样读操作只是volatile读操作,性能优于一个无竞争的锁获取的开销。但是当需要对该变量执行写操作,应该加锁。

  PS:这里ConcurrentHashMap也有加锁读的情况。利用方法  V readValueUnderLock(HashEntry<K,V> e)。只有value为空的时候,才会加锁读,这种情况就是编译器对value的赋值操作进行重排序了。

    感谢家纯师兄的订正和指导。
 
相关文章
|
存储 监控 NoSQL
一篇搞定Redis中的BigKey问题
BigKey的具体表现是redis中的key对应的value很大,占用的redis空间比较大,本质上是大value问题。
1623 0
|
关系型数据库 MySQL
cmd中输入net start mysql 提示:服务名无效或者MySQL正在启动 MySQL无法启动
cmd中输入net start mysql 提示:服务名无效或者MySQL正在启动 MySQL无法启动
|
7月前
|
Java
ArrayList扩容机制
本文解析了Java中`ArrayList`的扩容机制。
212 70
|
6月前
|
Serverless Python
借助 serverless 将 MCP 服务部署到云端
本文介绍了如何将 MCP 服务通过 SSE 协议部署到云端,避免本地下载和启动的麻烦。首先,使用 Python 实现了一个基于 FastMCP 的网络搜索工具,并通过设置 `transport='sse'` 启用 SSE 协议。接着,编写客户端代码测试服务功能,确保其正常运行。随后,利用阿里云函数计算服务(FC 3.0)以 Serverless 方式部署该服务,包括创建函数、配置环境变量、添加依赖层以及部署代码。最后,提供了客户端测试方法和日志排查技巧,并展示了如何在不同工具(如 Cherry-Studio、Cline 和 Cursor)中配置云端 MCP 服务。
1096 10
借助 serverless 将 MCP 服务部署到云端
|
10月前
|
人工智能 智能硬件
SPAR:智谱 AI 推出自我博弈训练框架,基于生成者和完善者两个角色的互动,提升了执行准确度和自我完善能力
SPAR 是智谱团队推出的自我博弈训练框架,旨在提升大型语言模型在指令遵循方面的能力,通过生成者和完善者的互动以及树搜索技术优化模型响应。
251 0
SPAR:智谱 AI 推出自我博弈训练框架,基于生成者和完善者两个角色的互动,提升了执行准确度和自我完善能力
|
JavaScript 前端开发 关系型数据库
事件管理工具:用Python和Vue打造在线预订和票务系统
【4月更文挑战第11天】构建一个在线预订和票务系统,结合Python(Flask或Django)后端与Vue.js前端。准备工作包括设置Python环境、Node.js、数据库和Git。后端创建RESTful API,Flask适合轻量级,Django提供完整框架。前端使用Vue CLI、Vuex和Vue Router构建用户界面。通过Vuex管理状态,Vue Router定义路由,Axios与后端通信。这种架构支持团队协作,代码维护和扩展。
180 0
|
存储 缓存 Java
spring的三级缓存,以及循环依赖的形成和解决(详细)
spring的三级缓存,以及循环依赖的形成和解决(详细)
1744 0
|
存储 NoSQL Redis
2)Redis 的键值对长什么样子,又是怎么存储的?
2)Redis 的键值对长什么样子,又是怎么存储的?
235 0
Java- IO 及其相关面试题(上)
Java- IO 及其相关面试题(上)
408 0
|
存储 设计模式 Java
Java面试题:解释代理模式的概念,并举例说明其应用场景。
Java面试题:解释代理模式的概念,并举例说明其应用场景。
247 0