Java内存及JSR-133个人的一些理解
1、JMM的建立
JMM【Java Memory Model】内存模型 是围绕着并发场景下如何处理原子性、可见性、有序性来建立的,
1.1、原子性
Java内存模型来直接保证的原子性变量操作包括read、load、assign、use、store和write这六个;如果应用场景需要一个更大范围的原子性保证(经常会遇到),Java内存模型还提供了lock和 unlock操作来满足这种需求可以使用synchronized【底层monitorenter + monitorexit】。
1.2、可见性
volatile + final + synchronized。
1.3、有序性
volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本 身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对 其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入。
1.4、总结
synchronized 很万能可以保证原子性+有序性+可见性
volatile可以保证可见性+有序性【不可以保证原子性】
2、happens-before和synchronized-with
happens-before可以解决不跨线程的有序性,保证了时序性,但是基于Happens-before的内存模型是较弱的,会发生一些违反因果关系的结果【当一个写操作发生在了一个其依赖的读操作之前,我们将这样的问题称为因果关 系,因为它涉及写操作是否会触发自身发生的问题。】,但是缺完全遵循happens-before规则,所以为了解决happens-before的问题JSR-133有提出了一个synchronized-with来解决。
Happens-Before and Synchronizes-With Edges If we have two actions x and y, we use x hb → y to mean that x happens-before y. If x and y are actions of the same thread and x comes before y in program order, then x hb → y.
2.1、synchronized-with
synchronized-with是个发生在两个不同thread间的同步行为,当A synchronized-with B的时,代表A对内存操作的效果,对于B是可见的。而A和B是两个不同的thread的某个操作。其实synchronized-with就是跨thread版本的happens-before。因此,Java必须要定义一些特殊的语法,像volatile, synchronized, final来确保针对同一个变数的跨thread内存操作能够正确的同步。
2.2、synchronized关键字可以保证
Mutual Exclusive【互斥性】 对同一个对象而言,不可能有两个前缀synchronized的方法同时交错执行,当一个thread正在执行前缀synchronized的方法时,其他想执行synchronized方法的thread会被挡住。 建立Happens Before关系 对同一个对象而言,当一个thread离开synchronized方法时,会自动对接下来调用synchronized方法的thread建立一个happens-before关系,前一个synchronized的方法对该对象所做的修改,保证对接下来进入synchronized方法的thread可见。【有序性】
要确保这件事情,代表JVM必须要做两件事,一个是在离开synchronized代码段时,把local processor的cache写入到内存内,另一个是在进入下一个synchronized前,要让local cache失效,使处理器重新去main memory抓正确的值。这样才能够确保每次进入synchronized代码段时,对象的状态是最新的。【内存可见性】
3、happens-before的顺序
4、synchronized-with的描述
某个管程 m 上的解锁动作 synchronizes-with 所有后续在 m 上的锁定动作 (这里的后续是根据同步顺序定义的)。
对 volatile 变量 v 的写操作 synchronizes-with 所有后续任意线程对 v 的读操 作(这里的后续是根据同步顺序定义的)。
用于启动一个线程的动作 synchronizes-with 该新启动线程中的第一个动作。
线程 T1 的最后一个动作 synchronizes-with 线程 T2 中任一用于探测 T1 是否 终止的动作。T2 可能通过调用 T1.isAlive()或者在 T1 上执行一个 join 动作 来达到这个目的。
如果线程 T1 中断了线程 T2,T1 的中断操作 synchronizes-with 任意时刻任 何其它线程(包括 T2)用于确定 T2 是否被中断的操作。这可以通过抛出 一个 InterruptedException 或调用 Thread.interrupted 与 Thread.isInterrupted 来实现。
为每个变量写默认值(0,false 或 null)的动作 synchronizes-with 每个线程 中的第一个动作。 虽然在对象分配之前就为该对象中的变量写入默认值看起来有些奇怪,从 概念上看,程序启动创建对象时都带有默认的初始值。因此,任何对象的 默认初始化操作 happens-before 程序中的任意其它动作(除了写默认值的 操作)。
调用对象的终结方法时,会隐式的读取该对象的引用。从一个对象的构造 器末尾到该引用的读取之间存在一个 happens-before 边缘。注意,该对象 的所有冻结操作 happen-before 前面那个 happens-before 边缘 的起始点。
5、如何判断是否被正确的同步
There are two key ideas to understanding whether a program is correctly synchronized:
Conflicting Accesses Two accesses (reads of or writes to) the same shared field or array element
are said to be conflicting if at least one of the accesses is a write.
Happens-Before Relationship Two actions can be ordered by a happens-before relationship.
If one action happens-before another, then the first is visible to and ordered before the second.
It should be stressed that a happens-before relationship between two actions does not imply that
those actions must occur in that order in a Java platform implementation. The happens-before
relation mostly stresses orderings between two actions that conflict with each other, and defines
when data races take place. There are a number of ways to induce a happens-before ordering,
including:
• Each action in a thread happens-before every subsequent action in that thread.
• An unlock on a monitor happens-before every subsequent lock on that monitor.
• A write to a volatile field happens-before every subsequent read of that volatile.
• A call to start() on a thread happens-before any actions in the started thread.
• All actions in a thread happen-before any other thread successfully returns from a join() on
that thread.
• If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.
理解一个程序是否被正确的同步了,有两个关键概念:
冲突访问(Conflicting Accesses) 对同一个共享字段或数组元素存在两个访问(读
或写),且至少有一个访问是写操作,就称作有冲突。
Happens-Before 关系 两个动作(action)可以被 happens-before 关系排序。如果一
个动作 happens-before 另一个动作,则第一个对第二个可见,且第一个排在第二个
之前。必须强调的是,两个动作之间存在 happens-before 关系并不意味着这些动作
在 Java 中必须以这种顺序发生。happens-before 关系主要用于强调两个有冲突的动
作之间的顺序,以及定义数据争用的发生时机。可以通过多种方式包含一个
happens-before 顺序,包括:
某个线程中的每个动作都 happens-before 该线程中该动作后面的动作。
某个管程上的 unlock 动作 happens-before 同一个管程上后续的 lock 动作。
对某个 volatile 字段的写操作 happens-before 每个后续对该 volatile 字段的读操作。
在某个线程对象上调用 start()方法 happens-before 该启动了的线程中的任意动作。
某个线程中的所有动作 happens-before 任意其它线程成功从该线程对象上的
如果某个动作 a happens-before 动作 b,且 b happens-before 动作 c,则有 a
happens-before c.
6、关联的DCL问题
public class Singleton {
private int i = 0;
private static Singleton instance = null;
public static Singleton getInstance() {
if(null == instance) {
instance = new Singleton(); // 1
i++; //2
}
return instance;
}
}
无论对于1;或2都会被即使编译器优化i++ 分为三步 取i、加1;赋值 ,1分为 new对象、初始化、关联引用
多线程情况下都会有可能被其他线程获取到初始化不完全的对象和变量。
所以需要使用volatile进行内存可见性的保证。
7、参考资料
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)
JSR133中文版.pdf
JSR-133 英文版 有问题及资源获取可以关注公众号👉 YangzaiLeHeHe 【回复 1 或者jsr】或者 添加我的微信Yang_sir_xu