volatile是Java虚拟机提供的轻量级的同步机制(“乞丐版”的synchronized)
- 保证可见性
- 不保证原子性
- 禁止指令重排
可见性
指当多个线程访问同一个变量时,如果其中一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
验证可见性demo:
import java.util.concurrent.TimeUnit;
class MyData {
volatile int number = 0;
public void addTo60() {
number = 60;
}
}
public class VolatileDemo {
public static void main(String[] agrs) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + "\t updated number: " + myData.number);
}, "AAA").start();
while (myData.number == 0) {}
System.out.println(Thread.currentThread().getName() + "\t mission is over");
}
}
结果:
AAA come in
main mission is over
AAA updated number: 60
不保证原子性
原子性:程序中的所有操作是不可中断的,要么全部执行成功要么全部执行失败
不保证原子性正是volatile轻量级的体现,多个线程对volatile修饰的变量进行操作时,会出现容易出现写覆盖的情况(i++)
验证不保证原子性demo:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData {
volatile int number = 0;
public void addPlusPlus() {
number++;
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addPlusPlus();
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
}
}
结果:
main finally number value: 19109
解决不保证原子性问题:Atomic
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyData {
volatile int number = 0;
public void addPlusPlus() {
number++;
}
AtomicInteger atomicInteger = new AtomicInteger();
public void addAtmic() {
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addAtmic();
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type,finally number value: "
+ myData.atomicInteger);
}
}
结果:
main finally number value: 19746
main AtomicInteger type,finally number value: 20000
禁止指令重排
指令重排:为了提高程序运行效率,编译器可能会对输入指令进行重新排序,即程序中各个语句的执行先后顺序同代码中的顺序不一定一致。(但是它会保证单线程程序最终执行结果和代码顺序执行的结果是一致的,它忽略了数据的依赖性)
源代码 -> 编译器优化重排 -> 指令并行重排 -> 内存系统重排 -> 最终执行指令
volatile能够实现禁止指令重排的底层原理:
- 内存屏障(Memory Barrier):它是一个CPU指令。由于编译器和CPU都能够执行指令重排,如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,任何指令都不能和该条Memory Barrier指令进行重排序,即通过插入内存屏障指令能够禁止在内存屏障前后的指令执行重排序
优化 - 内存屏障的另外一个作用是强制刷新各种CPU的缓存数据,因此任何CPU上的线程都能够读取到这些数据的最新版本。以上两点正好对应了volatile关键字的禁止指令重排序和内存可见性的特点
- 对volatile变量进行写操作时,会在写操作之后加入一条store屏障指令,将工作内存中的共享变量copy刷新回主内存中;对volatile变量进行读操作时,会在读操作之前加入一条load的屏障指令,从主内存中读取共享变量
应用场景:
-
高并发环境下DCL单例模式使用volatile
public class SingletonDemo { private static volatile SingletonDemo instance = null; private SingletonDemo() { System.out.println(Thread.currentThread().getName() + "我是构造方法SingletonDemo()"); } public static SingletonDemo getInstance() { if (instance == null) { synchronized (SingletonDemo.class) { if (instance == null) { instance = new SingletonDemo(); } } } return instance; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { SingletonDemo.getInstance(); }, String.valueOf(i)).start(); } } }
-
JUC包下AtomicXxx类:原子类AtomicXxx中都有一个成员变量value,该value变量被声明为volatile,保证
AtomicXxx类的内存可见性,而原子性由CAS算法&Unsafe类保证,结合这两点才能让AtomicXxx类很好地替代synchronized关键字。public class AtomicInteger extends Number implements java.io.Serializable { // ... private volatile int value; // ... }