volatile:
JVM 提供的轻量级的同步机制。保证了并发编程的可见性、有序性
不保证原子性
1、可见性:
定义:
有一个线程将主内存中的变量值做了修改,其他线程都将马上收到通知,立即获得最新值
- 写线程:JMM会把该线程对应的本地工作内存中的共享变量值刷新到主内存。
- 读线程:JMM会把该线程对应的本地工作内存置为无效,线程将到主内存中重新读取共享变量。
原理:
专业术语:
- 内存屏障(memory barriers):一组处理器指令,用于实现对内存操作的顺序限制。
- 缓存行(cache line):CPU高速缓存中可以分配的最小存储单位。处理器填写缓存行时会加载整个缓存行。
- 借助了CPU的lock指令,lock指令在多核处理器下,
- 可以将当前处理器的缓存行的数据写回到系统内存,
- 同时使其他CPU里缓存了该内存地址的数据置为无效
案例演示:
public class VolatileDemo {
volatile int number = 0;
public static void main(String[] args) {
VolatileDemo test1 = new VolatileDemo();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行时,number = "+test1.number);
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
test1.add();//暂停3秒后,修改number的值。
System.out.println(Thread.currentThread().getName()+"执行add()方法之后,number = "+test1.number);
}
},"t1").start();
while (test1.number == 0){}
System.out.println(Thread.currentThread().getName()+"程序结束!");
}
public void add(){
this.number = 10;
}
}
- 不加volatile,因为不在同一个线程内,一直陷入死循环
- 加了volatile,此变量信息一旦改变,会通知其他线程获取新值
- 将会把该线程对应的本地工作内存中的共享变量值刷新到主内存。
- 会把读线程对应的本地工作内存置为无效,线程将到主内存中重新读取共享变量。
2、有序性:
防止指令重排
指令重排序:编译器和处理器重排序,
JMM会分别限制这两种指令重排序。(通过内存屏障)
介绍:
执行程序时,为了提高计算性能,编译器和处理器常常会对指令进行重排序,一般分为如下3种:
- 源代码 ——> 编译器优化的重排 ——> 指令并行的重排 ——>内存系统的重排 ——> 最终执行的指令
- 单线程环境下,可以确保程序最终执行结果和代码顺序执行结果的一致性(单线程环境下不用关注指令重排,因为是否重排都不会出错)。处理器在进行重排序时,必须要考虑指令之间的数据依赖性。
- 多线程环境中,线程交替执行。由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果也就无法预测。
- 用volatile关键字修饰的变量,可以禁止指令重排序,从而避免多线程环境下,程序出现乱序执行的现象。
解决方案-内存屏障:
volatile写操作:前后均加内存屏障
volatile读操作:在后面插入两个内存屏障。
- 在每个volatile写操作的前面插入一个StoreStore屏障,防止写volatile与后面的写操作重排序。
- 在每个volatile写操作的后面插入一个StoreLoad屏障,防止写volatile与后面的读操作重排序。
- 在每个volatile读操作的后面插入一个LoadLoad屏障,防止读volatile与后面的读操作重排序。
- 在每个volatile读操作的后面插入一个LoadStore屏障,防止读volatile与后面的写操作重排序。
内存屏障 | 解释说明 |
---|---|
StoreStore屏障 | 禁止上面的普通写和下面的volatile 写重排序。 |
StoreLoad屏障 | 防止上面的volatile 写与下面可能存在的volatile 读/写重排序 |
LoadLoad屏障 | 禁止下面所有的普通读操作和上面的volatile读重排序。 |
LoadStore屏障 | 禁止下面所有的普通写操作和上面的volatile读重排 |
3、不保证原子性:
原子性定义:
要么不做,要么全做
- 当某个线程正在执行某件事情的过程中,是不允许被外来线程打断的。
- volatile不能保证原子性,即执行过程中是可以被其他线程打断甚至是加塞的。
解决方案:
- 操作方法加synchronized,悲观锁机制,重量级
- 具体操作属性变成原子类,如AtomicInteger,乐观锁机制,值刷新的时候加锁