多线程-volatile关键字

首先我们知道在java中内存的交互有以下几点。

Read Load 和 Store Write 两对操作不可分割。

Lock(锁定):作用于主内存的变量,他把变量标识为一个线程独占的状态。
UnLock(解锁) :作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
Read(读取):作用于主内存的变量,它把一个变量的值从主内存中读取到线程工作内存中,以便随后的load动作使用。
Load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本中。
Use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令将会执行这个操作。
Assign(赋值):作用于工作内存的变量,它把执行引擎接收到的值付给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
Store(存储):作用于工作内存的变量,它把工作内存中的一个变量的值传递到主内存中,以便随后的write操作使用。
Write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

Java内存模型还规定了在执行上述8种基本操作必须满足如下规则:
  1. 不允许read和load。store和write操作之一单独出现。
  2. 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  3. 不允许一个线程无原因的把数据从线程的工作内存同步回主内存。
  4. 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用了一个未被初始化的变量,换句话说,就是一个变量实施use、store操作之前,必须先执行过了assign和load操作。
  5. 一个变量在同一个时刻只允许一个线程对其进行lock操作,但lock操作可以被同一条操作重复执行多次,多次执行lock之后,只有执行相同次数的unlock操作,变量才会被解锁。
  6. 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新load和assign操作初始化变量的值。
  7. 如果一个变量实现没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去lock一个被其他线程锁定住的变量。
  8. 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中。

如果要把一个变量从主内存复制到工作内存,就要顺序地执行read和load操作,如果把变量从工作内存同步回主内存,就要顺序地执行store和write操作。



volatile的作用是,能够保证变量对线程的可见性,也就是说,变量值被其中一个线程更改后对其他线程是可见的,但是并不能保证复合操作的原子性(例如i++)。所以要想保证线程安全,还需要通过其他的方式,例如synchronized关键字或者Lock锁。
如果需要对基本类型进行原子操作,可以使用基本类型的Atomic封装类。例如AtomicInteger,里面封装了一系列保证原子性的操作方法。   
 
volatile是如何保证变量对其他线程的可见性?
1、每次对变量的读操作都从主内存中获取。
2、每次对变量的写操作都第一时间写到主内存中。
并且volatile禁止指令的重排序,对在读之前和写之后加入一个内存屏障这样可以保证:一个线程写入一个变量后,任何线程都会得到最新值

happens-before中的一条
volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。

可以通过一段代码来证明volatile是非线程安全的。


/**
* Created by kaijiyu on 2018/3/27.
*/
public class VolatileDemo {
    static volatile int x = 0;
    public static void add(){
        x++;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        add();
                    }
                }
            });
            thread1.start();
        }
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }
        System.out.println(x);
    }

}
这段代码每次执行的结果都是不一样的,如果能够保证线程安全的话,x的值应该始终为100000;
如果要保证线程安全,只需要给add()方法加一个synchronized关键字即可。

volatile关键字有时候会被错误的理解,认为只要对变量使用volatile关键字就会确保线程安全,其实并不是,volatile只能起到一个简单的同步作用。

只有在以下两种情况下,才可以保证原子性。
1、运算结果不依赖变量的当前值,或者只有单一线程修改变量值
2、变量不需要和其他状态变量共同参与不变约束



阅读更多

没有更多推荐了,返回首页