volatile是什么
volatile关键字是Java虚拟机提供的轻量级同步机制
JMM是什么
JMM是一个抽象概念,定义了共享内存系统中多线程程序读写操作共享内存的行为规范,保证共享内存的正确性(可见性、有序性、原子性)。JMM中关于同步的规定:
- 线程解锁前必须把共享变量最新的值刷新回主存中
- 线程加锁前必须从主存中读取共享变量最新的值到自己的工作内存
- 加锁和解锁必须是同一把
volatile关键字作用
- 保证内存可见性
- 禁止指令重排,保证有序性
volatile内存可见性代码演示
不加volatile
class MyData{
int i = 0;
public void addTo100() {
this.i = 100;
}
}
public class Demo {
public static void main(String[] args) {
// 创建共享变量
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "启动!");
// 睡眠3s
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo100();
System.out.println(Thread.currentThread().getName() + "数据修改完成!");
}, "线程A").start();
while (myData.i == 0) { // 主线程阻塞等待
}
System.out.println(Thread.currentThread().getName() + "检测到变量被修改,程序退出!");
}
}
运行结果
主线程进入阻塞等待,线程A对变量的修改对主线程是不可见的
加volatile
class MyData{
volatile int i = 0;
public void addTo100() {
this.i = 100;
}
}
public class Demo {
public static void main(String[] args) {
// 创建共享变量
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "启动!");
// 睡眠3s
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo100();
System.out.println(Thread.currentThread().getName() + "数据修改完成!");
}, "线程A").start();
while (myData.i == 0) { // 主线程阻塞等待
}
System.out.println(Thread.currentThread().getName() + "检测到变量被修改,程序退出!");
}
}
运行结果
线程A修改变量完成后立马被主线程感知到,程序退出
volatile不保证原子性代码演示
class MyData{
volatile int i = 0;
public void add() {
this.i ++;
}
}
public class Demo {
public static void main(String[] args) {
// 创建共享变量
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.add();
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2) { // 主线程礼让
Thread.yield();
}
System.out.println("最终结果:" + myData.i);
}
}
运行结果
假设volatile可以保证原子性,正确的结果应该是20000。i++可以看作是i = i+1,此命令包含三步操作:1.取出i值 2.执行i+1 3.重新赋值给i。如果线程A修改了i,但还没来得及写回主存,此时cpu被线程B抢占后也进行了修改,此时线程B拿到的就不是i的最新值,因此计算结果有偏差
volatile保证原子性解决
直接上代码
class MyData{
volatile AtomicInteger i = new AtomicInteger(); // 默认为0
public void add() {
this.i.addAndGet(1);
}
}
public class Demo {
public static void main(String[] args) {
// 创建共享变量
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.add();
}
}, String.valueOf(i)).start();
}
while (Thread.activeCount() > 2) { // 主线程礼让
Thread.yield();
}
System.out.println("最终结果:" + myData.i);
}
}
运行结果
JUC(java.util.concurrent)包中的AtomicXXX类通过CAS解决了原子性问题。
CAS:Compare And Swap(比较并交换),核心思想就是在变量重新赋值前,把线程工作空间内的变量副本值和变量实际内存空间中变量值做比较,如果相等就赋值,不相等就进入循环判断。