volatile关键字解析(一)

引起线程并发问题,可以简单的总结为以下三条:

  • 原子性问题
  • 可见性问题
  • 有序性问题(重排序问题)

原子性问题

什么是原子性?

原子性,即一个操作或者多个操作,要么全部执行并且执行过程中不会被任何因素打断,要么全部都不执行。

如常见的银行转账、count++操作等,都必须具备原子性才能保证不出现意外。

A向B转账100元,需要保证两步:A账户减100,B账户加100,如果有任何一个发生意外,总有不满意的一方,A账户减了100,B账户没有加100,客户肯定不满意;A账户由于某种原因没有减100,但是B账户却加了100,银行此时肯定也不干(毕竟是国内,你懂得)。

可见性问题

什么是可见性?

可见性是指当多个消除访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。

同样举个例子来说明一下:

public class RunThread extends Thread {

    private boolean isRunning = true;

    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    @Override
    public void run() {
        System.out.println("进入到run方法中了");
        while (isRunning == true) {
        }
        System.out.println("线程执行完成了");
    }
}
public class TestRun
{
    public static void main(String[] args) {
        try {
            RunThread thread = new RunThread();
            thread.start();
            Thread.sleep(100);
            thread.setRunning(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }  
}

我们会发现,RunThread线程无法正常结束了,进入了死循环了。这是由于在主线程中修改的isRunning值,并没有及时对RunThread线程感知到,这就是可见性的问题,由于主线程修改了之后,RunThread线程并没有立即能看到。

有序性问题
什么是有序性?
有序性即程序执行的顺序按照代码的先后顺序执行。

int a = 0;
int b = 0;
a = 1; //语句1
b = 2; //语句2
int c = a * a;//语句3 
int d = b + c;//语句4

从上述代码看,语句1在语句2之前执行,但是对于JVM来说情况未必如此,这就是指令重排序。

volatile使用场景
当需要避免并发时发生意想不到的结果,必须要保证以上三条都满足,否则极有可能得到你不想要的结果,我们常用的操作有synchronized和lock来保证这些情况,但是有些情况下,我们其实可以使用轻量级并发volatile关键字,
但是它并不能保证原子性操作,因此,volatile并不能替代synchronized。
关于volatile使用的场景:

  • 对变量的写操作不依赖于当前值
  • 该变量没有包含在具有其他变量的不变式中

常见的场景有:

  • 状态标记变量(如上文提到的RunThread例子中可以对isRunning变量使用volatile关键字)
  • 单例中双重检查锁,见(http://www.cnblogs.com/woniu4/p/8287484.html)

什么是指令重排序呢?简单多说,就是处理器为了提升程序的运行效率,可能对输入的代码进行优化,它不保证程序各个语句的执行先后顺序同代码中的顺利一致,但保证最后的结果和顺序执行是一直的。
由于语句4依赖于语句3,则两者的顺序是不会调整的。

原文地址:https://www.cnblogs.com/woniu4/p/8329618.html