并发编程的3个条件

1、 原子性:要实现原子性方式较多,可用synchronized、lock加锁,AtomicInteger等,但volatile关键字是无法保证原子性的;

2、可见性:要实现可见性,也可用synchronized、lock,volatile关键字可用来保证可见性;

3、有序性:要避免指令重排序,synchronized、lock作用的代码块自然是有序执行的,volatile关键字有效的禁止了指令重排序,实现了程序执行的有

 

volatile 作用 (有序性)

volatile 关键字并不是 Java 语言的特产,古老的 C 语言里也有,它最原始的意义就是禁用 CPU 缓存。

正确的双重检查锁定模式需要需要使用 volatile。volatile主要包含两个功能。

保证可见性。使用 volatile定义的变量,将会保证对所有线程的可见性。

禁止指令重排序优化。

由于 volatile禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。

注意,volatile禁止指令重排序在 JDK 5 之后才被修复

 示例:

/**
 * @Description:
 * @Package: lsr-microservice
 * @author: Hacker_lsr@126.com
 **/
public class VolatileTest {
    int x = 0 ;
    volatile boolean v = false;
    public void writer(){
        x = 42 ;
        v = true;
    }
    public void reader(){
        if (v = true){
            //   x = ? ; x = ? 
        }
    }
}

例如,我们声明一个 volatile 变量 volatile int x = 0,它表达的是:告诉编译器, 对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。这个语义看上去相 当明确,但是在实际使用的时候却会带来困惑。

例如下面的示例代码,假设线程 A 执行 writer() 方法,按照 volatile 语义,会把变量 “v=true” 写入内存;假设线程 B 执行 reader() 方法,同样按照 volatile 语义,线程 B 会从内存中读取变量 v,如果线程 B 看到 “v == true” 时,那么线程 B 看到的变量 x 是 多少呢?

直觉上看,应该是 42,那实际应该是多少呢?这个要看 Java 的版本,如果在低于 1.5 版 本上运行,x 可能是 42,也有可能是 0;如果在 1.5 以上的版本上运行,x 就是等于 42。

/**
 * @Description:
 * @Package: lsr-microservice
 * @author: Hacker_lsr@126.com
 **/
public class Instance {
    private static volatile Instance instance;
    public Instance(){}
    public static Instance getInstance(){
        if (instance==null){
            synchronized (Instance.class){ //  1
                if (instance==null){//  2
                    instance = new Instance();//  3
                }
            }
        }
        return instance;
    }
}

执行过程:

双重校验锁方式的执行过程如下:

1.线程A进入 getInstance() 方法。

2.由于 instance为 null,线程A在 //1 处进入 synchronized 块。

3.线程A被线程B预占。

4.线程B进入 getInstance() 方法。

5.由于 instance仍旧为 null,线程B试图获取 //1 处的锁。然而,由于线程A已经持有该锁,线程B在 //1 处阻塞。

6.线程B被线程A预占。

7.线程A执行,由于在 //2 处实例仍旧为 null,线程A还创建一个 instance 对象并将其引用赋值给 instance。

8.线程A退出 synchronized 块并从 getInstance() 方法返回实例。

9.线程A被线程B预占。

10.线程B获取 //1 处的锁并检查 instance 是否为 null。

11.由于 instance是非 null 的,并没有创建第二个 Instance 对象,由线程A所创建的对象被返回。