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 ...
随机推荐
- Educational Codeforces Round 55 (Rated for Div. 2) A - Vasya and Book
传送门 https://www.cnblogs.com/violet-acmer/p/10035971.html 题意: 一本书有n页,每次只能翻 d 页,问从x页到y页需要翻动几次? 注意:往前翻最 ...
- HomeFragment 嵌套关系
1.HomeFragment 在mainActivty 中调用: 2.HomeFragment 中: private ArrayList<Fragment> mFragments = ne ...
- (Bash博弈 大数) 51nod1068 Bash游戏 V3
1068 Bash游戏 V3 有一堆石子共有N个.A B两个人轮流拿,A先拿.每次拿的数量只能是2的正整数次幂,比如(1,2,4,8,16....),拿到最后1颗石子的人获胜.假设A B都非常聪明 ...
- bank_card.js
function init() { undefined = "undefined"; mkCClist(); } function ccchk(cdi) { output1 = & ...
- 怎么正确的回滚git的代码?
1. git reflog或者git log查看到节点的hash值 2. git reset滚回某个节点 1)如果想保留当前的代码用 git reset --mixed ${Hash} 2)如果想联通 ...
- Java Web之JSP
什么是JSP? JSP就是一个可以写Java代码的HTML页面 JSP是什么? JSP是Servlet,JSP的本质就是Servlet Tomcat的web.xml文件下有这样几段代码: 看到下面的通 ...
- 鼠标右键Table的td弹出多级菜单,双击td编辑
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...
- golang goroutine
goroutine-介绍 1)进程就是程序程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位2)线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位.3 ...
- html(常用标签,标签分类),页面模板, CSS(css的三种引入方式),三种引入方式优先级
HTML 标记语言为非编程语言负责完成页面的结构 组成: 标签:被<>包裹的由字母开头,可以结合合法字符( -|数字 ),能被浏览器解析的特殊符号,标签有头有尾 指令:被<>包 ...
- python保存文件到数据库
效果 model class WEIGUI_IMG(Base): __tablename__ = 'DW_ZHS_D_WEIGUI_IMG' # 表名 id = Column(), primary_k ...