volatile关键字

可见性

所谓可见性,就是指一个线程改变了共享变量之后其他线程能够立即知道这个变量被修改。我们知道在Java内存模型中,不同线程拥有自己的本地内存,而本地内存是主内存的副本。如果线程修改了本地内存而没有去更新主内存,那么就无法保证可见性。

退不出的循环

public class ThreadTest2 {
    private static boolean run= true;
    private static int i = 0;


    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            System.out.println("t1开始执行");
            while(run){
                i++;
            }
            System.out.println("t1结束执行" + i);
        },"t1").start();


        Thread.sleep(1000);
        state = false;
    }
}

main线程对run变量进行了修改,结果t1线程却依然无法停止

为什么?

  1. 初始状态,t线程刚开始从主内存读取了run的值到工作内存

  1. 因为t线程要频繁从主内存中读取run的值,JIT编译器会将state的值缓存至自己工作内存中的高速缓存中,减少对主存中run的访问,提高效率

  1. 1s之后,main线程修改了run的值,并同步至主存,而t是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是久值

解决方法

volatile(易变)关键字

    private static volatile boolean state = true;
  • 它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存

或者加synchronized关键字

注意:

  • 前面例子体现的实际是可见性,他保证的是在多个线程之间,一个线程对volatile变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况

  • synchronized语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性,但缺点是synchronized是属于重量级操作,性能相对更低

为什么synchronized关键字可以保证可见性呢?

synchronized在修改了本地内存中的变量后,解锁前会将本地内存修改的内容刷新到主内存中,确保了共享变量的值是最新的,也就保证了可见性


如果说在前面实例的死循环中加入System.out.println();会发现即使不加volatile修饰符,线程t也能正确看到对run变量的修改,为什么?

  • 通过看System.out.println();的源码不难发现
    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

println方法内部实际上也是加了synchronized关键字的,因此可以保证可见性和原子性


volatile原理

volatile的底层实现原理是内存屏障,Memory Barrier

  • 对volatile变量的写指令后会加入写屏障
  • 对volatile变量的读指令前会加入读屏障

如何保证可见性

  • 写屏障保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor1(I_Result r) {
    num = 2;
    ready = true;  //ready是 volatile 赋值 带写屏障
    // 写屏障
}
  • 而读屏障保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
    //读屏障
    // ready是 volatile 读取值带读屏障
    if(ready) {
        r.r1 = num + num;
    } else {
        r.r1 = 1;
    }
}

如何保证有序性

  • 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public void actor1(I_Result r) {
    num = 2;
    ready = true;  //ready是 volatile 赋值 带写屏障
    // 写屏障
}
  • 而读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
    //读屏障
    // ready是 volatile 读取值带读屏障
    if(ready) {
        r.r1 = num + num;
    } else {
        r.r1 = 1;
    }
}

注意:

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到他前面
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序

即只能保证有序性,可见性,不保证原子性

double-checked lock问题

以著名的double-checked locking单例模式为例

final class Singleton {
    private Singleton() {
    }

    private static Singleton singleton = null;

    public static Singleton getInstance() {
        //再加一层check,解决synchronized效率低的问题
        if (singleton == null) {
            //首次访问会同步,而之后的使用没有synchronized
            synchronized (Singleton.class) {
                //加锁解决线程安全问题,
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

以上实现的特点是:

  • 懒惰实例化
  • 首次使用getInstance()才使用synchronized加锁,后序使用时无需加锁
  • 但很关键一点:第一个if使用了singleton变量,是在同步代码块之外的

解决方法:

在singleton变量上加上 volatile 关键字

final class Singleton {
    private Singleton() {
    }

    private static volatile Singleton singleton = null;

    public static Singleton getInstance() {
        //再加一层check,解决synchronized效率低的问题
        if (singleton == null) {
            //首次访问会同步,而州的使用没有synchronized
            synchronized (Singleton.class) {
                //加锁解决线程安全问题,
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}

happens-before

happens-before规定了对共享变量的写操作对其他线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下happens-before规则,JVM并不能保证一个线程对共享变量的写,对于其他线程对该共享变量的读可见

  • 线程解锁A之前对变量的写,对于接下来对A加锁的其他线程对该变量的读可见
public class Test {
    private static final Object A = new Object();
    private static int x;

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (A){
                x=10;
            }
        },"t1").start();
        
        new Thread(() -> {
            synchronized (A){
                System.out.println(x);
            }
        },"t2").start();

    }
    
}
  • 线程对volatile变量的写,对接下来其他线程对该变量的读可见
public class Test {

    private static volatile int x;

    public static void main(String[] args) {

        new Thread(() -> {
                x=10;
        },"t1").start();

        new Thread(() -> {
                System.out.println(x);
        },"t2").start();
    }
}
  • 线程 start前对变量的写,对该线程开始后对该变量的读可见
public class getClassTest {

    private static volatile int x;
    
    public static void main(String[] args) {

        x = 10;

        new Thread(() -> {
                System.out.println(x);
        },"t2").start();
    }
}
  • 线程结束前对变量的写,对其他线程得知他结束的读可见(比如其他线程调用t1.isAlive()或t1.join()等待他结束)
public class getClassTest {
    private static int x;

    public static void main(String[] args) throws InterruptedException {
        
        Thread t2 = new Thread(() -> {
                x = 10;
        },"t2");
        t2.start();
        
        t2.join();
        System.out.println(x);
    }
}
  • 线程t1打断t2(interrupt)前对变量的写,对于其他线程得知t2被打断后对变量的读可见(通过t2.interrupted或t2.isInterrupted)
public class getClassTest {
    private static int x;

    public static void main(String[] args) throws InterruptedException {

        Thread t2 = new Thread(() -> {
                while(true){
                    if (Thread.currentThread().isInterrupted()){
                        System.out.println(x);
                        break;
                    }
                }
        },"t2");
        t2.start();

        new Thread(() ->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            x = 10;
            t2.interrupt();
        },"t1").start();

        while (!t2.isInterrupted()) {
            Thread.yield();
        }
        System.out.println(x);
    }
}
  • 对变量默认值(0,false,null)的写,对其他线程对该变量的读可见
  • 具有传递性,如果x hb-> y 并且 y hb -> z那么有x hb -> z,配合volatile的防指令重排,有下面的例子
public class getClassTest {
    private static int x;
    static int y ;

    public static void main(String[] args) throws InterruptedException {

        Thread t2 = new Thread(() -> {
            y = 10;
            x = 20;
        //------写屏障------
        },"t2");
        t2.start();

        Thread t1 = new Thread(() -> {
        //------读屏障------
            System.out.println(x);
            System.out.println(y);
        },"t2");
        t1.start();
    }
}

线程安全单例

单例模式有很多种实现方式,饿汉,懒汉,静态内部类,枚举类,那么哪些是线程安全的呢

饿汉式: 类加载就会导致该单例对象被创建

懒汉式:类加载不会导致该单例对象被创建,而是首次使用该对象时才会创建

实现1:

//类加final的原因:防止创建子类使得方法被子类重写
//如果实现了序列化接口,则需要再创建一个public Object readResolve()方法来防止通过反序列化来创建对象
final class Singleton1 implements Serializable {
    //防止通过构造方法来创建对象,但是不能防止反射创建新的实例
    private Singleton1() {
    }

    //静态成员变量在类加载阶段进行赋值,类加载阶段是有锁的,可以保证线程安全
    private static final Singleton1 singleton1 = new Singleton1();

    //为什么提供静态方法而不是直接将 单例对象设置为public的?
    //1. 提供更好的封装性
    //2. 能够提供泛型的支持
    public static Singleton1 getInstance() {
        return singleton1;
    }
    
    public Object readResolve(){
        return singleton1;
    }

}

实现2:

enum Singleton {
    singleton2;
    
    public void whateverMethod(){
        
    }
}

实现3:

public final class Singleton {
    private Singleton() {}
    
    private static Singleton singleton3 = null;
    
    //这里线程安全,但是只有第一次创建线程会出现线程安全问题,之后的每次创建都会被synchronized锁住,效率低
    public static synchronized Singleton getInstance(){
        if(singleton != null){
            return singleton3;
        }
        singleton3 = new Singleton();
        return singleton3;
    }
}

实现4:

public final class Singleton{
    private Singleton() {}
    
    //构造方法的指令和赋值指令可能发生重排序,加入volatile关键字阻止synchronized内重排序
    private static volatile Singleton singleton4 = null;
    
    public static Singleton getInstance(){
        
        //解决频繁synchronized效率低的问题
        if(singleton4 ==null){
            synchronized(Singleton.class){
                //解决首次创建单例的并发问题
           		 if(singleton4 == null){
           			singleton4 = new Singleton();
        		}	
        	}	
        }
        return singleton4;
    }
}

实现5:

public final class Singleton {
    private Singleton() {}
    
    //静态内部类只有在调用的时候才会被加载
    private static class lazyHolder{
        static final Singleton singleton5 = new Singleton();
    }
    
    //线程安全
    public static synchronized Singleton getInstance(){
        return lazyHolder.singleton5;
    }
}
posted @ 2021-03-02 20:13  longda666  阅读(88)  评论(0)    收藏  举报