volatile是java提供的一种轻量级的同步机制。java中包含两种同步机制:同步块(方法)和volatile变量,相当于synchronized,volatile更轻量级,因为它不会引起线程上下文的切换和调度。
二.并发编程的3个基础概念 1.原子性 定义:一个操作或者多个操作,那么全部都执行且在执行的过程中不会被任何因素打扰,要么都失败。java中原子性操作包括:
- 基本类型的读取和赋值,且赋值必须是值赋给变量,变量之间的相互赋值不是原子性操作。
- 所有引用reference的赋值操作
- java.concurrent.Atomic.* 包中所有类的一切操作
当一个变量被volatile修饰,就表示着线程的本地内存无效,当一个线程修改了共享变量,会立即刷新到主内存中,其他线程读取变量,直接从主内存中读取。synchronize和lock锁都可以保证可见性,它们都保证同一时刻只有一个线程获取锁并执行同步代码,并且在锁释放之前,将变量的修改刷新到主内存中,保证可见性。
3.有序性 定义:程序执行的顺序按照代码的先后顺序执行在java内存模型中,为了效率时允许编译器和处理器对指令进行重拍,当然重排不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
三.锁的互斥和可见性锁提供了两种主要特性:互斥和可见性
- 互斥:一次只能有一个线程获取锁,一次也就只有一个线程能使用共享资源。
- 可见性:确保在一个线程释放锁之前,要把修改过后的数据刷新到主内存中。
要使用volatile提供理想的线程安全必须要满足两点:
1.对变量的写操作不依赖当前值。
2.该变量没有包含在具有其他变量的不变式中。
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
- 当写一个volatile变量时,JMM会把该线程中本地内存中的值刷新到主内存中;
- 写操作会导致其他线程中的volatile变量缓存无效。
重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。重排序需要遵守一定规则:
1. 重新排序操作不会对存在数据依赖关系的操作进行排序。
比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
2.重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。
使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则:
a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行; b.在进行指令优化时,不能将对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。 即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。五.volatile的原理
volatile可以保证线程的可见性并且提供一定的有序性,但无法保证原子性。在JVM底层volatile采用"内存屏障"来实现的,内存屏障有三个功能:
- 它确保执行到内存屏障的时候,在它前面的操作都已经执行完成;
- 他会强制将本地内存中的值刷新到主内存中;
- 如果是写操作,他会导致其他cpu中对应的缓存无效。
仅仅使用一个Boolean标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。
volatile boolean shutdownRequested;
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
独立观察
使用 public volatile String lastUser; 保存最后一个用户状态(每次被新来的用户替换),供其他方法读取。



