1. JMM
8种操作执行顺序
lock read load use assign store write unlock
- lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
- read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
- load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
- use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
- store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
JMM对8种内存交互操作制定的规则:
- 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
- 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
- 不允许线程将没有assign的数据从工作内存同步到主内存。
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
- 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
- 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。
2、Volatile关键字(重点)
概念
Volatile 是 JVM 提供的轻量级的同步机制
- 保证可见性(当某个线程修改volatile变量时,jmm会将这个变量修改更新到主存中,并让其他线程的工作内存的存储副本失效)
- 不保证原子性
- 避免指令重排
加上synchronized关键字 、Lock锁可以保证原子性
解决使用volatile保证原子性的解决方案:
使用java.util.concurrent.atomic 下的一个工具包,配合volatile支持单个变量上的无锁线程安全编程。
使用方法:
- 创建 可原子更新的int 值对象
private static volatile AtomicInteger num = new AtomicInteger(0); - 该对象有方法+1,原理是调用Unsafe类的方法,底层使用的CAS
num.getAndIncrement();
例子:
import java.util.concurrent.atomic.AtomicInteger;
public class Demo1 {
// 创建 可原子更新的int 值对象
private static volatile AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
// 该对象有方法+1,底层使用的CAS
num.getAndIncrement();
}
}).start();
}
// 确保上面的线程执行完,只剩下 main 和 gc 线程
while(Thread.activeCount() > 2){
Thread.yield();//当前主线程 暂时停止执行
}
System.out.println("正常结果为10000,输出结果为===》" + num); // 正常结果为10000,输出结果为===》10000
}
}
避免指令重排
程序代码的执行并不一定会按照 源代码的顺序执行。
加入volatile会避免指令重排,内存中有屏障
作用:
- 保证特定的执行顺序
- 可以保证某些变量的内存可见性