1、简介
volatile是Java提供的一种轻量级的同步机制。
Java包含两种内在同步机制:同步代码块(同步方法块) 以及 volatile关键字。
volatile相比于synchronized(重量级锁),volatile不会引起线程上下文的切换和调度,但是其同步性差,稳定性也不高
2、并发的三个基本概念(先介绍使用volatile的前提)
(1)原子性
关于原子性的定义:
① 一个操作或者多个操作,要么全部执行,且执行过程中不会被任何因素打断。要么就全部都不执行
② 原子性是不支持多线程操作的,无论是多核还是单核,具有原子性的量同一时刻只能有一个线程对该量操作。
③ 整个操作过程中不会被线程调度器中断的操作,均可被认为是原子性(注:a++和++a不是原子性操作)
属于原子性操作的范围:
① 基本类型的读取和赋值操作,且赋值必须是数字赋值给变量,变量之间不是原子操作
② 所有引用(reference)的操作:强引用(常用引用)、弱引用(debug之类)、软引用等
③ java.util.concurrent.Atomic.* 包中所有类的一切操作
(2)可见性
可见性的定义:
① 程序执行的顺序是按照代码的先后顺序执行(由上往下)
② 内存模型中的有序性可以理解为:在本线程观察,所有操作都是有序的(线程内表现为 “串行语义” )
如果A线程观察B线程,则所有操作都是无序的(指令重排序、工作内存主内存同步延迟)
③ 可以通过synchornized和lock来保证有序性,synchornized和lock宝成每个时刻都是一个线程执行同步代码,
相当于让线程有序地执行,保证了有序性
(3)锁的互斥性和可见性
锁的主要特性:互斥(mutual exclusion)和可见性(visibility)
(3)Java的内存模型(JMM)以及共享变量的可见性
注:JMM是一个抽象的内存模型,本地私有内存、主内存都是抽象概念,并非一定真实对应CPU缓存和物理内存
① JMM决定一个线程对共享资源的更改何时对下一个线程可见
(5)volatile关键字变量的特性
① 保证可见性,但不保证原子性
Ⅰ.当写一个volatile变量时,JMM会把该线程本地私有内存中的变量强制刷新到主内存中去
Ⅱ.该write操作会导致其他线程中的缓存无效
Ⅰ.重排序操作不会对有数据依赖关系的操作进行重排序,例如s = 1; b = s;这种有依赖关系的操作不重排序
Ⅱ.重排序是为了优化性能,前提是不能改变单线程下的执行结果,例如a=1,b=2,c=a+b;过程可以重排序,结果不变
(6)volatile关键字变量的原理
① volatile可以保证线程可见性有一定的有序性,但无法保证原子性(需用到内存屏障,会产生前缀命令lock)
Ⅰ.确保重排序时不会将该变量后面的指令排序到内存屏障(即volatile变量)前,也不会将前面的指令放在其后面
Ⅱ.会强制将本地私有缓存的变量刷新到主内存中
Ⅲ.如果是写操作,会导致其它CPU对应的缓存行无效
应用场景:重排序在单线程下一定可以保证结果的正确性,但是多线程下重排序操作则有可能会导致结果的非正确性
public class MyVolatile {
int a = 1;
boolean status = false;
//状态设置为true
public void changeStatus(){
/**
* 以下两行并不存在依赖关系,
* 重排序时可能存在先执行 status=true,再执行a=2
*/
a = 2;
status = true;
}
/**
* 但此时已经判断了 status = true,则会进入下面的run方法判断
* 因为重排序操作,a = 2可能还未执行,所以有可能 b = a + 1最终输出的结果还是2而不是3
*/
//当状态为true,则运行
public void run(){
if(status){
int b = a + 1;
System.out.println(b);
}
}
}
解决方案:使用volatile关键字修饰共享变量阻止这种重排序,在编译时,volatile会在指令序列中插入内存屏障来禁止特定类型的处理器重排序
volatile禁止重排序也有以下规则
① 当执行到volatile变量的读写操作时,在其前面的更改操作肯定已经全部执行,且结果也是对后面的操作可见的,
而且在该变量后面的操作也一定没有执行(前面的搞定,后面获取军情后按兵不动)
② 在进行指令优化时,不能将volatile变量访问的语句放在其后面执行,也不能将后面的语句放在该变量前面执行
也就是执行到volatile变量时,该变量前面所有语句全部执行完毕,该变量后面语句全部未执行
不适用使用volatile关键字的场景:volatile不适合复合操作(重复操作)
问题:myVolatile变量不是原子操作,可以由读取、加、赋值组成、所以结果不能达到3000
// 不适合使用volatile的场景 public class MySychronized { public volatile int myVolatile = 0; public void volatileCrease(){ myVolatile++ ; } public static void main(String[] args){ final MySychronized ms = new MySychronized(); for(int i=0; i<10; i++){ new Thread(){ public void run(){ for(int j=0; j<1000; j++){ ms.volatileCrease(); }; }.start(); } while(Thread.activeCount() > 1) //保证前面的线程执行完 Thread.yield(); System.out.println(ms.volatileCrease); } } }
解决方案:可以采用synchronized或者是Lock来解决这个问题,或者采用Java并发包中的原子操作类
① 采用synchronized
// 使用sychronized
public class Mysynchronized{
public int mySource = 0;
public synchronized void SynchronizedCrease(){
mySource++;
}
public static void main(String[] args){
final Mysynchronized ms = new Mysynchronized();
for(int i=0; i<10; i++){
new Thread(){
public void run(){
for(int j=0; j<1000; j++){
ms.SynchronizedCrease();
};
}.start();
}
while(Thread.activeCount() > 1) //保证前面的线程执行完
Thread.yield();
System.out.println(ms.volatileCrease);
}
}
}
② 采用Lock
//使用Lock
public class MyLock{
public int mySource = 0;
public synchronized void LockCrease(){
mySource++;
}
public static void main(String[] args){
final MyLock ms = new MyLock();
for(int i=0; i<10; i++){
new Thread(){
public void run(){
for(int j=0; j<1000; j++){
ms.LockCrease();
};
}.start();
}
while(Thread.activeCount() > 1) //保证前面的线程执行完
Thread.yield();
System.out.println(ms.LockCrease);
}
}
}
③ 采用Java并发包中的原子操作类,该类是通过CAS循环方式来保证原子性的
//采用Java并发包的原子操作类
public class MyAtomic{
public int mySource = 0;
public synchronized void AtomicCrease(){
mySource++;
}
public static void main(String[] args){
final MyAtomic ms = new MyAtomic();
for(int i=0; i<10; i++){
new Thread(){
public void run(){
for(int j=0; j<1000; j++){
ms.AtomicCrease();
};
}.start();
}
while(Thread.activeCount() > 1) //保证前面的线程执行完
Thread.yield();
System.out.println(ms.AtomicCrease);
}
}
}
应用场景:状态标记量
// 状态标记量
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
volatile boolean inited = false;
//线程 1:
context = loadContext();
inited = true;
//线程 2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
应用场景:单例模式中使用双重锁
// 单例模式中使用双重锁
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}