所以这个程序是存在数据竞争的。
结合Pentium和PowerPC处理器中广泛使用的一种主贺消流的缓存一致性协议--MESI来解释下。
假设CPU1执大拍陆行线程A,CPU2执行线程滚顷B;CPU1和CPU2都有高速缓存;线程A和线程B共享volatile变量started,
对于没有被同步的共享变量,
【参考资料】
Java语言中有一个“先行发生”(happens-before)的原则。拆唤 这个原则非常重要,它是判断数据是否存在竞争、 线程是否安全的主要依据,依靠这个原则,我们可以通过几条规则一揽子地解决并发环境下两个 *** 作之间是否可能存在冲突的所有问题。先行发生是Java内存模型中定义的两项 *** 作之间的偏序关系,如果说 *** 作A先行发生于 *** 作B,其实就是说在发生 *** 作B之前, *** 作A产生的影响能被 *** 作B观察到。“影响”包括修改了内存中共享变量的值、 发送了消息、 调用了方法等。 这句话不难理解,但它意味着什么呢?我们可以举个例子来说明一下,如下伪代码:
/帆御李/以下 *** 作在线程A中执行
i=1;
//以下 *** 作在线程B中执行
j=i;
//以下 *** 作在线程C中执行
i=2;
假设线程A中的 *** 作“i=1”先行发生于线程B的 *** 作“j=i”,那么可以确定在线程B的 *** 作执行后,变量j的值一定等于1,得出这个结论的依据有两个:一是根据先行发生原则,“i=1”的结果可以被观察到;二是线程C还没“登场”,线程A *** 作结束之后没有其他线程会修改变量i的值。 现在再来考虑线程C,我们依然保持线程A和线程B之间的先行发生关系,而线程C出现在线程A和线程B的 *** 作之间,但是线程C与线程B没态迟有先行发生关系,那j的值会是多少呢?答案是不确定!1和2都有可能,因为线程C对变量i的影响可能会被线程B观察到,也可能不会,这时候线程B就存在读取到过期数据的风险,不具备多线程安全性。
二、Java内存模型中的先行发生关系
下面是Java内存模型下一些“天然的”先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。 如果两个 *** 作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们随意地进行重排序:
1. 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的 *** 作先行发生于书写在后面的 *** 作。 准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、 循环等结构。
2. 管程锁定规则(Monitor Lock Rule):一个unlock *** 作先行发生于后面对同一个锁的lock *** 作。 这里必须强调的是同一个锁,而“后面”是指时间上的先后顺序。
3. volatile变量规则(Volatile Variable Rule):对一个volatile变量的写 *** 作先行发生于后面对这个变量的读 *** 作,这里的“后面”同样是指时间上的先后顺序。
4. 线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
5. 线程终止规则(Thread Termination Rule):线程中的所有 *** 作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、 Thread.isAlive()的返回值等手段检测到线程已经终止执行。
6. 线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。
7. 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
8. 传递性(Transitivity):如果 *** 作A先行发生于 *** 作B, *** 作B先行发生于 *** 作C,那就可以得出 *** 作A先行发生于 *** 作C的结论。
volatile在Java并发编程中常用于保持内存可见性知局纳和防止指令重排序。
内存可见性(MemoryVisibility):所有线程都能看到共享内存的最新状态。
防止指令重排:在基于偏序关系的Happens-Before内存模型中,指令重排技术大大提高了程序执行效率,但同时也引入了一些问题。
volatile保持内存可见性的特殊规则:read、load、use动作必须连续出现;assign、store、write动作必须连续出现;每次读取前必须先从主内存刷新最新的值。
每次写入后必须立即同步回主内存当中。也就是说,volatile关键字修饰的变量看到的随时是自己的最新值。在线程1中对变量v的最新修改,对线程2是可见的。
volatile防止指令重排的策略:腊悔在每个volatile写 *** 作的前面插入一个StoreStore屏障;在每个volatile写 *** 作的后面插入一个StoreLoad屏障。
在每个volatile读 *** 作的后面插入一个LoadLoad屏障;在每搭没个volatile读 *** 作的后面插入一个LoadStore屏障。注意:只有在Happens-Before内存模型中才会出现这样的指令重排序问题。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)