volatile

  volatile 可以看做是一种轻量级的synchronized实现,volatile实现的仅仅是synchronized的部分功能,但是开销较synchronized小。特定的情形下,使用volatile会更为合适。  

  synchronized提供了两种主要特性:互斥 和可见性互斥即同一时刻只允许一个线程持有某个特定的锁,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

  volatile具有 synchronized 的可见性特性,但是不具备互斥性和原子特性。这就是说多个线程能够自动发现 volatile 变量的最新值。

  

  关于可见性解释如下:

  

  类的成员变量是存储在堆内存中的,堆内存的运行速度较栈内存的运行速度要慢的多。所以Java多线程在处理成员变量时,是在每个线程的工作内存中拷贝一份成员变量,线程内完成对成员变量的操作后再写入主内存中。

  上面的读取,写入都不是原子的,ThreadA对成员变量的修改,ThreadB无法立即看到,因此ThreadB工作内存中的成员变量还是旧的值,这就是多线程所产生的数据不一致。

  volatile修饰的成员变量,线程读取的不是线程工作内存中的副本,而是直接从主内存中读取最新的值。volatile变量修改时,会立即写入到主内存中。

  关于volatile还有一点需要解释,就是happens-before原则,先看下面的例子: 

package multiThread;

class VolatileExample {
    static int a, i = 0;
    static volatile boolean flag = false;
    
    public static void writer() {
        a = 1;         // 1
        flag = true;   // 2
    }
    
    public static void reader() {
        if (flag) {   // 3
            i = a;    // 4
        }
        System.out.println("i= " + i);
    }
    
    public static void main(String [ ] args) {
        for (int i = 0; i < 200; i++) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    writer();
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    reader();
                }
            });
            
            thread1.start();
            thread2.start();
        }
    }
}

  上面的例子运行结果全为1,而没有出现0。

  在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。程序中写在前面的语句可能会被放在后面执行,happens-before原则对重排序做了限制,符合happens-before原则时,前一个操作执行的结果必须对后一个操作可见。

  happens-before原则中与volatile相关的部分:   

        volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。

      传递性:如果A happens- before B,且B happens- before C,那么A happens- before C  

  上述例子程序中的的happens before 关系可以分为两类:

  根据传递性规则,1 happens before 2; 3 happens before 4

  根据volatile规则,2 happens before 3

  根据happens before 的传递性规则,1 happens before 4

  上述happens before 关系的图形化表现形式如下:

  

  使用volatile相比synchronized更容易出错,使用volatile要符合如下的2个条件:

  1. 对volatile的赋值操作不依赖于当前值,例如i++操作,当i定义为volatile时,无法保证线程间看到的值是一致的。i++操作是非原子的,会分解为读取、自增、赋值来完成。

  2. volatile变量不能出现在不等式中,例如a<b。

  使用volatile的典型的场景:

  1.状态标志:用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。  

volatile boolean shutdownRequested = false;

...

public void shutdown() { shutdownRequested = true; }

public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}

  shutdown方法可能在另外一个线程中调用,当shutdown方法将shutdownRequested的值置为true时,其他线程会立即看到值的变化。

  2. 安全的发布:后台线程在启动阶段从数据库加载一些数据。其他线程在数据加载完成后才能使用 

public class BackgroundFloobleLoader {
    public static volatile Flooble theFlooble;

    public void initInBackground() {
        // do lots of stuff
        theFlooble = new Flooble();  // this is the only write to theFlooble
    }
}

public class SomeOtherClass {
    public void doWork() {
        while (true) { 
            // do some stuff...
            // use the Flooble, but only if it is ready
            if (BackgroundFloobleLoader.theFlooble != null) 
                doSomething(BackgroundFloobleLoader.theFlooble);
        }
    }
}

  

  3. 观察者: 一个后台线程每隔几秒读取一次volatile 变量的最新值,并更新。其他线程可以读取这个变量,可以保证其他线程读到的值是一致的,而且是最新的。可以在一些支持并发的Java容器中见到volatile的使用,比如ConcurrentHashMap。

  由于volatile修改后可以保证其他线程读到的值是一致的,而且是最新的。可以结合volatile和synchronized实现读时不加锁,写时加锁,如果读操作远远超过写操作,那么能很大的提高效率。 

public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
     private volatile int value;

    public int getValue() { return value; }

    public synchronized int increment() {
        return value++;
    }
}

  

  

原文地址:https://www.cnblogs.com/lnlvinso/p/4614719.html