【JMM】-同步原语之final重排序规则
重排序规则写final域的重排序规则:
禁止对final域的写重排序到构造函数之外 编译器会在final域写之后,构造函数return之前,插入一个storestore屏障,这个屏障可以禁止处理器把final域的写重排序到构造函数之外。
禁止在构造函数对一个final修饰的对象的成员域的写入与随后将这个被构造的对象的引用赋值给引用变量重排序。
读final域重排序规则:
在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM会禁止这两个操作的重排序。处理器会在读final域操作的前面插入一个LoadLoad屏障。
参考:Java并发系列六:深入了解final关键字
【JMM】同步原语之volatile
引言Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。线程对volatile变量的修改会立刻被其他线程所感知,即不会出现数据脏读的现象,从而保证数据的可见性。
语义
保证了不同线程对这个变量进行操作时的可见性。
禁止进行指令重排序。
happens-before中的volatilevolatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
禁止重排序通过内存屏障来阻止指令重排序。JMM内存屏障图:volatile重排序规则表(”NO”表示禁止重排序。):策略:
在每个volatile写操作的前面插入一个StoreStore屏障;
在每个volatile写操作的后面插入一个StoreLoad屏障;
在每个volatile读操作的后面插入一个LoadLoad屏障;
在每个volatile读操作的后面插入一个LoadStore屏障。
盗用两张图片来加深理解,来自《java并发编程的艺术》:
参考:聊聊并发(一)深入分析Volatile的实现原理内存屏障和 volatile 语义正确使 ...
【JMM】同步原语之Synchronized-底层分析
Mark Word
锁信息存储在对象头的Mark Word中,在synchronized锁中,这个存储在对象头的Mark Word中的锁信息是一个指针,它指向一个monitor对象(也称为管程或监视器锁)的起始地址。monitor:(源码在这里)
12345678910111213141516171819ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; _S ...
【JMM】同步原语之Synchronized-简单介绍
作用synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
修饰的对象
修饰代码块,作用范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象
修饰方法,作用范围是整个方法,作用的对象是调用这个方法的对象
修饰静态方法,作用范围是整个静态方法,作用的对象是这个类的所有对象
修饰类,作用范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象
注意
当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
synchronized关键字不能继承。对于父类中用synchronized 修饰的方法,子类在覆盖该方法时,默认情况下不是同步的,必须显式的使用 synchronized 关键字修饰才行, 当然子 ...
【JMM】内存模型之内存屏障
引言内存屏障是为了解决在cacheline上的操作重排序问题。
作用:强制cpu将store buffer中的内容写入到cacheline中强制cpu将invalidate queue中的请求处理完毕
类型
屏障类型
指令示例
说明
LoadLoad Barriers
Load1;LoadLoad;Load2
该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作
StoreStore Barriers
Store1;StoreStore;Store2
该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作
LoadStore Barriers
Load1;LoadStore;Store2
确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作
StoreLoad Barriers
Store1;StoreLoad;Load1
该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作.它会使该屏障之前的所有内存访问指令(存储指令和访问指 ...
【JMM】内存模型之多线程内存可见性-happens-before
引言在并发编程时,会碰到一个难题:即一个操作A的结果对另一个操作B可见,即多线程变量可见性问题。解决方法就是提出了happens-before概念,即一个操作A与另一个操作B存在happens-before关系。
定义:《Time,Clocks and the Ordering of Events in a Distributed System》点击查看论文。
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
前提:操作A happens-before 操作B。对于第一条,编码时,A操作在B操作之前,则执行顺序就是A之后B。对于第二条,如果重排序后,虽然执行顺序不是A到B,但是最终A的结果对B可见,则允许这种重排序。
规则:
程序次序规则:一个线程内,按照代码顺序,书 ...
【JMM】内存模型之顺序一致性
引言顺序一致性是多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证,在顺序一致性执行过程中,所有动作之间的先后关系与程序代码的顺序一致。
JMM对正确同步的多线程程序的内存一致性做出的保证:如果程序是正确同步的,程序的执行将具有顺序一致性(sequentially consistent)。
特性
一个线程中的所有操作必定按照程序的顺序来执行。
所有的线程都只能看到一个单一的执行顺序,不管是否同步。
每个操作都必须原子执行且立即对所有程序可见。
盗用两张图:内存模型之顺序一致性
加了锁
未加锁
【JMM】内存模型之重排序
定义
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
例如,如果一个线程写入值到字段 a,然后写入值到字段 b,而且 b 的值不依赖于 a 的值,那么,处理器就能够自由的调整它们的执行顺序,而且缓冲区能够在 a 之前刷新 b 的值到主内存。
数据依赖
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。
如图所示,A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。因此在最终执行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序。
as-if-serial语义
as-if-serial语义的意思是,所有的操作均可以为了优化而被重排序,但是你必须要保证重排序后执行的结果不能被改变,编译器、runtime、处理器都必须遵守as-if-serial语义。注意as-if-serial只保证单线程环境,多线程环境下无效。
as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担 ...
【JMM】内存模型之结构抽象
引言内存模型结构的抽象分为两个层次:
多核CPU与内存之间
Java多线程与主存之间
多核CPU与内存之间因为CPU的运行速度与内存之间的存取速度不成正比,所以,引入了多级缓存概念,相应的也引出了缓存读取不一致问题,当然缓存一致性协议解决了这个问题(本文不深入讨论)。结构抽象如图:
Java多线程与主存之间JMM规定了所有的变量都存储在主内存(Main Memory)中。
每个线程有自己的工作内存(Working Memory),线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(volatile变量仍然有工作内存的拷贝,但是由于它特殊的操作顺序性规定,所以看起来如同直接在主内存中读写访问一般)。不同的线程之间也无法直接访问对方工作内存中的变量,线程之间值的传递都需要通过主内存来完成。如图:
【JMM】线程通信机制之共享内存与消息传递
引言在并发编程中,线程之间相互交换信息就是线程通信。目前有两个机制:
内存共享:java使用这种机制
消息传递:actor模型即是异步,非阻塞的一个消息传递,一切皆是Actor。Akka是对于Java的actor模型类库,用于构建高并发、分布式、可容错、事件驱动的基于JVM的应用。
内存共享方式,必须通过锁或者CAS技术去获取或者修改共享的变量,看起来比较简单,但是锁的使用难度比较难,业务复杂的话还有可能发生死锁。消息传递方式就是显示的通过发送消息来进行线程间通信,对于大型复杂的系统,可能优势更足。