验证乱序执行的程序示例 DisOrder.java:
public class DisOrder { private static int x = 0, y = 0; private static int a = 0, b = 0; // 以下程序可能会执行比较长的时间 public static void main(String[] args) throws InterruptedException { int i = 0; for (;;) { i++; x = 0; y = 0; a = 0; b = 0; Thread one = new Thread(() -> { // 由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间. shortWait(100000); a = 1; x = b; }); Thread other = new Thread(() -> { b = 1; y = a; }); one.start(); other.start(); one.join(); other.join(); String result = "第" + i + "次 (" + x + "," + y + ")"; if (x == 0 && y == 0) { // 出现这个分支,说明指令出现了重排 // 否则不可能 x和y同时都为0 System.err.println(result); break; } else { // System.out.println(result); } } } public static void shortWait(long interval) { long start = System.nanoTime(); long end; do { end = System.nanoTime(); } while (start + interval >= end); } }如上示例,如果指令不出现乱序,那么x和y不可能同时为0,通过执行这个程序可以验证出来,在我本机测试的结果是:
执行到第1425295次 出现了x和y同时为0的情况。
原子性程序的原子性是指整个程序中的所有操作,要么全部完成,要么全部失败,不可能滞留在中间某个环节;在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所打断。
一个示例:
class T { m = 9; }对象T在创建过程中,背后其实是包含了多条执行语句的,由于有CPU乱序执行的情况,所以极有可能会在初始化过程中生成以一个半初始化对象t,这个t的m等于0(还没有来得及做赋值操作)
所以,不要在某个类的构造方法中启动一个线程,这样会导致this对象逸出,因为这个类的对象可能还来不及执行初始化操作,就启动了一个线程,导致了异常情况。
volatile一方面可以保证线程数据之间的可见性,另外一方面,也可以防止类似这样的指令重排,所以
所以,单例模式中,DCL方式的单例一定要加volatile修饰:
具体可以参考设计模式学习笔记 中单例模式的说明。
CAS比较与交换的意思
举个例子:
内存有个值是3,如果用Java通过多线程去访问这个数,每个线程都要把这个值+1。
之前是需要加锁,即synchronized关键字来控制。但是JUC的包出现后,有了CAS操作,可以不需要加锁来处理,流程是:
第一个线程:把3拿过来,线程本地区域做计算+1,然后把4写回去,
第二个线程:也把3这个数拿过来,线程本地区域做计算+3后,在回写回去的时候,会做一次比较,如果原来的值还是3,那么说明这个值之前没有被打扰过,就可以把4写回去,如果这个值变了,假设变为了4,那么说明这个值已经被其他线程修改过了,那么第二个线程需要重新执行一次,即把最新的4拿过来继续计算,回写回去的时候,继续做比较,如果内存中的值依然是4,说明没有其他线程处理过,第二个线程就可以把5回写回去了。
流程图如下:
CAS会出现一个ABA的问题,即在一个线程回写值的时候,其他线程其实动过那个原始值,只不过其他线程操作后这个值依然是原始值。
如何来解决ABA问题呢?
我们可以通过版本号或者时间戳来控制,比如数据原始的版本是1.0,处理后,我们把这个数据的版本改成变成2.0版本, 时间戳来控制也一样,
以Java为例,AtomicStampedReference这个类,它内部不仅维护了对象值,还维护了一个时间戳。
当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳。
当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值,写入才会成功。
因此,即使对象值被反复读写,写回原值,只要时间戳发生变化,就能防止不恰当的写入。
CAS的底层实现Unsafe.cpp-->Atom::cmpxchg-->Atomic_linux_x86_inline.hpp-->调用了汇编的LOCK_IF_MP方法
Multiple_processor
lock cmpxchg