深入理解java虚拟机之volatile关键字

volatile关键字

  • 保证多线程间进行变量操作时的可见性
  • 禁止指令重排序优化

首先了解下什么是原子操作:原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。将整个操作视作一个整体是原子性的核心特征。
比如,变量的自增操作 k++,分为三个步骤:
1. 从内存中读取出变量 j 的值;
2. 将变量j 的值加1;
3 . 将变量j加1后的值写回内存。
这说明j++并不是一个原子操作。因为,它分成了三步,有可能当某个线程执行到了第2步时被中断了,意味着只执行了其中的两个步骤,没有全部执行。
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。而volatile很容易被不正确地使用,用来进行原子性操作。看如下代码:

public class VolatileTest {
    public static volatile int count=0;

    public static void increase(){
        for (int i = 0; i < 10; i++) {
            count++;
        }

        System.out.println("count= "+count);
    }

    public static void main(String[] args){
        Thread[] threads = new Thread[100];
        for(int i = 0;i < 100;i++){
            threads[i] = new Thread(new Runnable(){
                public void run(){
                    for(int j = 0;j<100;j++){
                        increase();
                    }
                }
            });
            threads[i].start();
        }
    }
}

运行结果如下所示:
深入理解java虚拟机之volatile关键字_第1张图片
这段代码发起了100个线程,按照程序预期的结果,对count自增操作的结果为100000,而实际结果99993小于100000,并且每次运行完结果都不一样。

原因是:volatile修饰的变量并不保证对它的操作(自增)具有原子性。(对于自增操作,可以使用JAVA的原子类AutoicInteger类保证原子自增)比如,假设j自增到25,线程A从主内存中读取j,值为25,将它存储到自己的线程空间中,执行加1操作,值为26。此时,CPU切换到线程B执行,从主从内存中读取变量j的值。由于线程A还没有来得及将加1后的结果写回到主内存,线程B就已经从主内存中读取了j,因此,线程B读到的变量 i 值还是5相当于线程B读取的是已经过时的数据了,从而导致线程不安全性。

你可能感兴趣的:(java虚拟机)