JUC知识点-volatile

1.JUC介绍

在Java5.0以上的版本新增了java.util.concurrrent包,此包在并发编程中有很多实用的工具,包括线程池、异步、IO和轻量级框架等实现;

2.并发的三个特性回顾

原子性

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

可见性

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

有序性

即程序执行的顺序按照代码的先后顺序执行。

3.volatile 关键字

volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

保证可见性,不保证原子性

当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去,但是这个写会操作会导致其他线程中的缓存无效;

禁止指令重排

在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行;

volatile不适用的场景

volatile不适合复合操作

i++不是一个原子性操作,可以由读取、加、赋值3步组成,需要配合synchronized或者lock来实现;.

volatile原理

volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用memory barriers(内存屏障)来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

内存屏障的作用

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2. 它会强制将对缓存的修改操作立即写入主存;
  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。

单例模式的双重锁为什么要加volatile

public class App {
    private volatile static App instance;
    public static App getInstance() {
        if(instance==null) {
            synchronized (App.class) {
                if(instance==null) {
                    instance= new App();
                }
            }
        }
        return instance;
    }
}

需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第7行会出现问题。instance= new App();可以分解为3行伪代码
a.memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址
上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第7行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。