JAVA锁和volatile的内存语义&volatile的使用场景
JAVA锁的内存语义
当线程释放锁时,JMM(Java Memory Model)会把该线程对应的本地内存中的共享变量刷新到主内存中。
当线程获取锁时,JMM会将该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
对比锁释放-读取的内存语义与volatile写-读的内存语义可以看出,锁释放与volatile写具有相同的内存语义;锁获取与volatile读具有相同的内存语义。
下面对锁释放和锁获取的内存语义做个总结。
- 线程1释放一个锁,实质上是线程1向接下来将要获取这个锁的某个线程发出了(线程1对共享变量所做修改的)消息。
- 线程2获取一个锁,实质上是线程2接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
- 线程1锁释放,随后线程2获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
锁内存语义的实现
锁有很多种,但其基本原理都是差不多的。书上是以ReentrantLock中的公平锁与非公平锁作为案例分析,有兴趣的同学可以去阅读原籍和源码。现总结如下:
- 公平锁和非公平锁释放时,最后都要写一个volatile变量state。
- 公平锁获取是,首先会去读volatile变量。
- 非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和写的内存语义。
所以锁释放-获取的内存语义的实现至少有下面两种方式:
1)利用volatile变量的写-读所具有的内存语义。
2)利用CAS所附带的volatile读和volatile写的内存语义。
由此可知:并发包下的类的实现方式大部分都是基于这两种方式实现的。
参考 https://www.cnblogs.com/yuanfy008/p/9346925.html
1.volatile的内存语义与实现
1.1 volatile读写的内存语义
volatile读写的内存语义 与 锁获取/锁释放 的内存语义对应相同
这两条保证了volatile修饰变量的可见性。
那JMM如何能够保证volatile实现其内存语义的,简单来说就是通过内存屏障。如果看过volatile变量汇编后的指令代码就会在代码中发现一句:
lock add1 $0x0
它的简单含义就是要把工作内存中的共享变量值刷新到主内存中,相当于加入内存屏障。
1.2 volatile有序的内存语义
volatile的另一个特性是禁止指令重排序,这里的内存语义我们可以总结为:
volatile读之后 的操作不会被重排序到 volatile读之前
volatile写之前 的操作不会被重排序到 volatile写之后
先volatile写–后volatile读,不可重排序
JMM通过插入内存屏障来实现以上语义,实质上有四种内存屏障策略:
volatile写操作前插入StoreStore屏障
volatile写操作后插入StoreLoad屏障
volatile读操作前插入LoadLoad屏障
volatile读操作后插入LoadStore屏障
其中StoreLoad屏障是全能型屏障,可以完成其他3个屏障的功能。所以它被大部分CPU支持。不同CPU有着不同的重排序规则,但是这一套JMM屏障策略可以完成所有类型CPU下的volatile语义。例如对于x86的CPU,它本身只支持写-读操作的重排序,对读-读,读-写,写-写操作的重排序都不支持;那么我们只需要加入StoreLoad屏障来避免写-读操作的重排序即可实现volatile语义。
亲测:volatile修饰的变量,对其相关操作是不具有原子性的
eg:volatile修饰的共享变量a 的自增操作 a++,在多个线程中运行;最后a的值很有可能小于++的执行次数。(因为a++ 实际是 a=a+1,是加法和赋值两个操作,不是原子操作。volatile不保证原子性,所以出现了小于++执行次数的结果)
volatile修饰的共享变量 ArrayList<Object> list,添加元素的操作list.add(new Object()),在多个线程中运行;最后list.size()也很有可能小于add执行的次数。(原因同上,list.add也不是原子操作)
对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
volatile的不能完全取代synchronized的位置,只有在一些特殊的场景下,才能适用volatile。总的来说,必须同时满足下面两个条件volatile修饰的变量才能保证在并发环境的线程安全:
(1)对变量的写操作不依赖于当前值。 --待思考
(2)该变量没有包含在具有其他变量的不变式中。 --待思考
三种类型的指令重排序
为了提高性能,编译器和处理器可能会对指令做重排序。重排序可以分为三种:
(1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
(2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
(3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
参考 http://www.cnblogs.com/paddix/p/5374810.html
JAVA锁和volatile的内存语义&volatile的使用场景的更多相关文章
- volatile的内存语义与应用
volatile的内存语义 volatile的特性 理解volatile特性的一个好方法是把对volatile变量的单个读/写,堪称是使用同一个锁对这些单个读/写操作做了同步. 锁的happens-b ...
- Java并发编程原理与实战四十二:锁与volatile的内存语义
锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...
- Java内存模型-volatile的内存语义
一 引言 听说在Java 5之前volatile关键字备受争议,所以本文也不讨论1.5版本之前的volatile.本文主要针对1.5后即JSR-133针对volatile做了强化后的了解. 二 vol ...
- Java中锁的实现与内存语义
目录 1. 概述 2. 锁的内存语义 3. 锁内存语义的实现 4. 总结 1. 概述 锁在实际使用时只是明白锁限制了并发访问, 但是锁是如何实现并发访问的, 同学们可能不太清楚, 下面这篇文章就来揭开 ...
- volatile的内存语义
volatile的特性 理解volatile特性的一个好方法是把对volatile变量的单个读/写,堪称是使用同一个锁对这些单个读/写操作做了同步. 锁的happens-before规则保证释放锁和获 ...
- 【java多线程系列】java中的volatile的内存语义
在java的多线程编程中,synchronized和volatile都扮演着重要的 角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性,可见性指的是当一 ...
- Java-内存模型 final 和 volatile 的内存语义
前提:内存屏障 内存屏障(Memory Barrier)与内存栅栏(Memory Fence)是同一个概念. 用于阻止指令重排序.保证了特定操作的执行顺序和某些变量的内存可见性. JMM 内存屏障分为 ...
- volatile内存语义
全面理解Java内存模型(JMM)及volatile关键字 volatile的内存语义 Volatile读写所建立的happens-before关系Volatile读写的内存语义 锁: 获取和释放Vo ...
- 基础篇:深入JMM内存模型解析volatile、synchronized的内存语义
目录 1 java内存模型,JMM(JAVA Memory Model) 2 CPU高速缓存.MESI协议 3 指令重排序和内存屏障指令 4 happen-before原则 5 synchronize ...
随机推荐
- springcloud实战案例苏宁和海信
http://springcloud.cn/view/341 为什么springlcoud不选用zookeeper: http://www.infoq.com/cn/articles/why-does ...
- java中一维数组的定义和遍历
public class ArrayDemo1{ public static void main(String[] args){ //1 定义数组 并同时赋值 int[] arr = new int[ ...
- 上这个资源网站,让你轻松无忧找mac软件资源
之前分享过好几篇关于mac软件相关的文章(想要看其他的mac软件专题文章,可以关注我,点击进入查看发表的文章),有网友表示,优质的软件推荐清单有了,想要下载和获取mac软件,买一个正版软件在APP s ...
- servlet模拟SpringMVC
1. web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi=&q ...
- 剑指Offer_编程题_3
题目描述 输入一个链表,从尾到头打印链表每个节点的值. /** * struct ListNode { * int val; * struct ListNode *next; * ListNode(i ...
- 运维监控-使用Zabbix Server 创建触发器Triggers
运维监控-使用Zabbix Server 创建触发器Triggers 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 1>.点击相应主机的触发器 2>.点击创建触发器 ...
- iptables之端口限速
#iptables -A FORWARD -p tcp -m tcp --sport 10000 -m limit --limit 500/sec --limit-burst 1000 -j ACCE ...
- Xstart Insatll And Usage
不进入 Linux 桌面环境,又需要运行一些图形化的软件,比如 Oracle 数据库的安装等 安装 Windows 上 Xstart 安装:https://www.cnblogs.com/jhxxb/ ...
- Web API中的Help Page
一.自动创建带帮助的WebAPI 第一步 创建项目的时候选择WebAPI 如下图所示,生成的项目会自动生成帮助文档 第二步 设置调用XML文档的代码 第三步 设置项目注释XML文档生成目录 项目— ...
- 清理sql2012数据库日志
--1.先把数据库设置为简单模式(右击数据库名->点'属性'->点'选项'->恢复模式改成'简单'->点'确定'按钮,--2.再执行下面的语句(或者右击数据库点'任务'-> ...