第一章 volatile关键字概述
1.1 多线程下变量的不可见性
1.1.1 概述
在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改后的变量的最新值。
通过volatile关键字,可以让该变量值可见。
package com.dhu.concurrency.volatil.concurrent;
/**
* 目标:多线程下变量访问的不可见性现象
* 1.定义一个成员变量
* 2.开启两个线程,一个线程负责修改,另外一个负责读取。
* @author zhou
*
*/
public class VisibilityDemo01 {
public static void main(String[] args) {
//开启一个子线程
MyThread myThread = new MyThread();
myThread.start();
//主线程
while(true) {
if(myThread.isFlag()) {
System.out.println("主线程循环执行");
}
}
}
}
class MyThread extends Thread {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//触发 修改共享成员变量
flag = true;
System.out.println(flag);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
可以看到,子线程中已经将flag设置为true,但main()线程中始终没有读到修改后的最新值
1.1.2 问题分析
1.1.3 小结
可见性问题的原因:
所有共享变量都存在于主内存中,每个线程有自己的本地内存,而且线程读取共享数据也是通过本地内存交换的,所以导致了可见性问题。
1.2 变量不可见性解决方案
第一种是加锁,第二种是使用volatile关键字。
1.2.1 解决方案1 - 加锁
加锁能够保证多线程下访问共享变量的可见性的原因:
1.2.2 volatile关键字修饰
使用volatile关键字修饰该变量:
//共享的成员变量
private volatile boolean flag = false;
工作原理:
第5步,会通过底层的总线机制去通知主线程中的变量 flag = false 已经失效,因为共享变量 flag 加了 volatile,其它线程就相当于有了一个嗅探机制,会去关注主内存中的变量是否被其它线程修改。一旦有被修改,就会将原来的工作内存变量副本失效,那么必然需要去主内存获取最新的flag变量值。
总结:一个线程修改了volatile修饰的变量,当修改写回主内存时,另外的线程立即看到最新的值。
第二章 volatile的其它特性
2.1 volatile特性概述
volatile可以实现并发下共享变量的可见性,此外:
volatile的原子性问题:volatile不能保证原子性操作。
禁止指令重排序:volatile可以防止指令重排序操作。
2.2 volatile不保证原子性
原子性:并发修改下的一中安全机制
2.2.1 问题原理说明
2.2.2 volatile原子性测试
多线程环境下,volatile可以保证共享数据的可见性,但不能保证对数据操作的原子性。对线程,volatile修饰的变量也是线程不安全的。
//共享变量
private volatile int count = 0;
小结:多线程下,要保证数据的安全性,还需要使用锁机制。
2.2.3 问题解决 - 使用锁机制
使用加锁机制保证volatile修饰变量的原子性操作。
每一个线程获得锁,执行完之后,通过volatile关键字可以获取count的最新值。
2.3 禁止指令重排序
2.3.1 概述
什么是重排序?
为了提高性能,编译器和处理器(cpu)常常会对既定的代码执行顺序进行指令重排序。
2.3.2 重排序的好处
重排序可以提高处理的速度
2.3.3 重排序问题案例演示
重排序虽然可以提高执行的效率,但是在并发执行下,JVM虚拟机底层并不能保证重排序带来的安全性问题。
确实出现了:cpu进行了指令的重排序
2.3.4 volatile禁止重排序
volatile修饰变量后可以实现禁止指令重排序,从而修正重排序可能带来的并发安全问题
public volatile static int a = 0, b = 0; //volatile修饰
public volatile static int i = 0, j = 0; //volatile修饰
3、volatile使用场景
3.1 纯赋值操作
不能保证原子性,赋值操作没有问题(变量是可见的)
4、volatile与synchronized的区别
4.1 区别
- volatile只能修饰实例变量和类变量(volatile的目的是保证多线程下变量访问的可见性。当一个线程修改该变量,其它线程可以看到其最新值),而synchronized(同步机制,多线程下线程安全)可以修饰方法以及代码块。
- volatile保证数据的可见性,但是不保证原子性(多线程读写操作,不保证线程安全),而synchronized是排他(互斥)机制
- volatile用于禁止指令重排序:volatile修饰的变量,在执行过程中可以用于禁止指令重排序,可以解决单例双重检查对象初始化代码执行乱序问题。
- volatile可以看作轻量版的sychronized,volatile不保证原子性;但是如果是多个线程对一个共享变量进行赋值,而没有其它操作,那么可以用volatile代替synchronized。因为赋值操作本身是原子性,而volatile有保证可见性,所以可以保证线程安全。
5、volatile总结