目录
- 概述
- 案例
- 代码如下
- 执行结果
- 原理
- 实现内存可见性的过程
- 硬件上的内存屏障
- 底层分析
- java中的四种内存屏障
- 缺陷
- 代码
- 执行结果
- 结束
概述
想要多线程程序正确的执行,必须要保证原子性、可见性及有序性。只要有一个没有被保证,就有可能导致程序运行不正确。
案例
代码如下
package com.fun.demo;public class DemoTicketVolatile {public static void main(String[] args) {TicketTask task = new TicketTask();new Thread(task, "窗口1").start();task.flag = false;System.out.println(task.flag);}static class TicketTask implements Runnable {public volatile boolean flag = true;@Overridepublic void run() {System.out.println("了线程执行");while (flag) {}System.out.println("了线程结束");}}
}
执行结果
原理
java语言对 volatile
的定义:java允许线程访问共享变量,为了确保共享变量被准确和一致的更新,线程应保证通过排他锁单独获得这个变量,即 volatile
可以保证多线程场景下变量的 可见性
和 有序性
。如果某变量用 volatile
修饰,则可以确保所有线程看到变量的值是一致的。
注意:volatiile
使用起来简单,但用好并不容易,对变量的操作一般会选择 JUC
中原子类,boolean
是它的一种常见用法。
实现内存可见性的过程
volatile 实现内存可见性的过程:
- 线程写 volatile 变量的过程:
- 1.改变线程本地内存中 volatile 变量副本的值
- 2.将改变后副本的值从本地内存刷新至主内存
- 线程读 volatile 变量的过程:
- 1.从主内存中读取 volatile 变量的最新值至线程的本地内存中
- 2.从本地内存中读取 volatile 变量的副本
硬件上的内存屏障
Load屏障,在x86上是 ifence
指令,在其它指令前插入 ifence
指令,可以让cpu寄存器中的数据失效,强制当前线程从主内存里面加载数据到cpu寄存器中。
Store屏障,在x86上是 sfence
指令,在其它指令后插入 sfence
指令,能让当前线程写入cpu寄存器的最新数据 ,写入到主内存,并让其它线程可见。
底层分析
volatile 实现内存可见性原理:内存屏障 (Memory Barrier)
内存屏障是一种cpu指令(系统层面的操作),用于控制特定条件下的重排序和内存可见性问题。
java编译器也会根据内存屏障的规则禁止重排序。
- 写操作时,通过在写操作指令后加入一条store屏障指令,让本地内存中变量的值能够刷新到主内存中
- 读操作时,通过在读操作前加入一条load屏障指令,及时读取到变量在主内存的值
java中的四种内存屏障
屏障 | 例子 | 详解 |
---|---|---|
LoadLoad屏障 | Load1;LoadLoad;Load2 | LoadLoad中两个Load,前一个对应Load1,后一个对应Load2;保证Load1从主内存里读取过程完成,在Load2及后续读操作之前。 |
StoreStore屏障 | Store1;StoreStore;Store2 | StoreStore中两个Store,前一个对应Store1存储代码,后个对应Store2存储代码,在Store2及后续写操作执行前,保证Store1的写入操作已将数据写入到主内存里,确认Store1的写入操作对其它线程可见。 |
LoadStore屏障 | Load1;LoadStore;Store2 | LoadStore中的Load对应Load1加载代码,Store对应Store2存储代码,在Store2及后结代码写入操作执行前,保证Load1从主内存里读取完毕所需要的数据。 |
StoreLoad屏障 | Store1;StoreLoad;Load2 | 在Load2及后续读取操作之前,保证Store1的写入操作已经将数据写入到主内存里,确认Store1的写入操作对其它处理器可见。 |
缺陷
代码
package com.fun.demo;public class DemoTicketVolatile {public static void main(String[] args) {TicketTask task = new TicketTask();new Thread(task, "窗口1").start();new Thread(task, "窗口2").start();new Thread(task, "窗口3").start();}static class TicketTask implements Runnable {private volatile int tickets = 100;@Overridepublic void run() {while (true) {if (tickets > 0) {try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "-正在卖票:" + tickets--);}}}}
}
执行结果
由下图可知,tickets--
不是原子性操作。导致执行出了问题。
结束
至此,volatile基本原理及缺陷
就结束了,如有疑问,欢迎评论区留言。