javacode工作生活

深入理解volatile

2019-07-02  本文已影响0人  九点半的马拉

Java内存模型

在计算机中,所有的运算操作都是由CpU的寄存器来完成的,在CPU Cache模型没出来之前,CPU所访问的数据只能是计算机的主存,但CPU本身的计算速度与主内存的读写速度远远不一致,所以在中间添加了Cache模型,在程序运行的时候,程序会把从内存中读取的数据复制一份到Cache中,然后直接对CPU cache中的数据进行读取和写入,当运算结束后,再将CPU cache中的最新数据刷新到主内存中。

cpu通过Cache与主内存进行交互.png

cpu通过Cache与主内存进行交互.png

但在多线程情况下,利用该机制,可能会出现缓存不一致的现象。
典型的解决办法有:

java内存模型.png

java内存模型.png

并发编程的三个重要特性

JMM与原子性

在Java中,对基本数据类型的变量读取赋值操作都是原子性的,对引用类型的变量读取和赋值的操作也是原子性的。

jMM与可见性

java中提供了以下三种方式来保证可见性

volatile关键字解析

该类变量具备下面两层语义:

  1. 保证了不同线程之间对共享变量操作时的可见性,也就是说当一个线程修改volatile修饰的变量,另一个线程会立即看到最新的值。
  2. 禁止对指令进行重排序操作。
    但volatile不具备原子性
    有一个经典的例子 i++操作
    如果有两个线程执行该操作时,当一个线程读取到i的当前值后,停止,然后跳转到另一个线程读取i的值,然后执行+1操作,并将值返回到主内存中,有的读者可能会产生这样的疑惑:
    volatile具有可见性,当一个线程修改后,另一个线程会立即看到最新的值,所以当第一个线程暂停回来后会从主内存中读取到最新的值,并执行+1操作,但实际情况是两个线程的结果是一样的,比如i=10,两个线程执行 volatile i++操作,都得到了11,理想的结果是一个11,一个12,为什么呢?
    volatile的可见性保证你每次访问到该变量时,都会读取到最新的值,但是并不会更新你已经读的值,它也无法更新你已经读了的值。上文中第一个线程是读取后再停止的,此时i值还没有被修改,当另一个线程修改完成后,该线程继续执行接下来的i+1操作,此时的i已经是已被读的值了,不会到主内存中获取最新的值,保留的是最初的值,所以产生了错误。

volatile的使用场景

  1. 开关控制利用可见性的特点
 public class MyThread extends Thread {
 private volatile boolean started = true;
 @Override
 public void run() {
     while(started) {
         
     }
 }
 public void shutdown(){
     this.started = false;
 }
}
  1. 状态标记利用了顺序性特点
private volatile boolean init = false;
private Context context;
public Context load() {
    if (!init) {
        context = loadContext();
        init = true; //防止重排序
    }
    return context;
}
  1. Singleton设计模式的double-check也是利用了顺序性特点

volatile和synchronized

  1. 使用上的区别
  1. 对原子性的保证
  1. 对可见性的保证
    都能保证多线程间的可见性,但是实现机制不同。
  1. 对有序性的保证
  1. 线程是否阻塞
上一篇下一篇

猜你喜欢

热点阅读