volatile关键字及其作用(举例说明)

volatile关键字及其作用

一、多线程并发编程中主要围绕着三个特性实现。

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

原子性
原子性指的一个操作或一组操作要么全部执行,要么全部不执行。

有序性
有序性是指程序执行的顺序按照代码的先后顺序执行。

二、volatile 除了解决共享变量的可见性,还有别的作用吗?

volatile 除了让共享变量具有可见性,还具有有序性(禁止指令重排序)。

三、举几个实际volatile 实际项目中的例子?

1.状态标志
比如我们工程中经常用一个变量标识程序是否启动、初始化完成、是否停止等,如下:volatile关键字及其作用(举例说明)_第1张图片volatile 很适合只有一个线程修改,其他线程读取的情况。volatile 变量被修改之后,对其他线程立即可见。

如果不加volatile 修饰,会有什么后果?
比如这是一个带前端交互的系统,有A、 B二个线程,用户点了停止应用按钮,A 线程调用shutdown() 方法,让变量shutdown 从false 变成 true,但是因为没有使用volatile 修饰, B 线程可能感知不到shutdown 的变化,而继续执行 doWork 内的循环,这样违背了程序的意愿:当shutdown 变量为true 时,代表应用该停下了,doWork函数应该跳出循环,不再执行。

2.懒汉式单例模式
volatile关键字及其作用(举例说明)_第2张图片
使用volatile 修饰保证 singleton 的实例化能够对所有线程立即可见。

1.为什么使用volatile 修饰了singleton 引用还用synchronized 锁?
volatile 只保证了共享变量 singleton 的可见性,但是 singleton = new Singleton(); 这个操作不是原子的,可以分为三步:

步骤1:在堆内存申请一块内存空间;

步骤2:初始化申请好的内存空间;

步骤3:将内存空间的地址赋值给 singleton;

所以singleton = new Singleton(); 是一个由三步操作组成的复合操作,多线程环境下A 线程执行了第一步、第二步之后发生线程切换,B 线程开始执行第一步、第二步、第三步(因为A 线程singleton 是还没有赋值的),所以为了保障这三个步骤不可中断,可以使用synchronized 在这段代码块上加锁。

2.第一次检查singleton 为空后为什么内部还需要进行第二次检查?
A 线程进行判空检查之后开始执行synchronized代码块时发生线程切换(线程切换可能发生在任何时候),B 线程也进行判空检查,B线程检查 singleton == null 结果为true,也开始执行synchronized代码块,虽然synchronized 会让二个线程串行执行,如果synchronized代码块内部不进行二次判空检查,singleton 可能会初始化二次。

3.volatile 除了内存可见性,还有别的作用吗?
volatile 修饰的变量除了可见性,还能防止指令重排序。

指令重排序 是编译器和处理器为了优化程序执行的性能而对指令序列进行重排的一种手段。现象就是CPU 执行指令的顺序可能和程序代码的顺序不一致,例如 a = 1; b = 2; 可能 CPU 先执行b=2; 后执行a=1;

singleton = new Singleton(); 由三步操作组合而成,如果不使用volatile 修饰,可能发生指令重排序。步骤3 在步骤2 之前执行,singleton 引用的是还没有被初始化的内存空间,别的线程调用单例的方法就会引发未被初始化的错误。
指令重排序也遵循一定的规则:

重排序不会对存在依赖关系的操作进行重排
volatile关键字及其作用(举例说明)_第3张图片
重排序目的是优化性能,不管怎样重排,单线程下的程序执行结果不会变。
volatile关键字及其作用(举例说明)_第4张图片
因此volatile 还有禁止指令重排序的作用。

你可能感兴趣的:(多线程,java)