前言
12.1 概述
12.2 硬件的效率与一致性
cpu的速度要比内存快的多。通过加入一层缓存来解决速度之间的矛盾,但是引入了新的问题一致性。在进行读写的时候需要遵守一致性协议。处理器会对代码进行乱序执行,也就是重排序。
12.3 Java内存模型
12.3.1 主内存与工作内存
为什么要定义java内存模型?
定义程序中各种变量的访问规则,关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节
Java内存模型规定了什么?
所有的变量都存储在主内存。
每条线程还有自己的工作内存。
线程的工作内存中保存了被该线程使用的变量的主内存副本。
线程对变量的所有操作(读取、赋值等)都必须在工作内 存中进行,而不能直接读写主内存中的数据。
不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
12.3.2 内存间交互操作
12.3.3 对于volatile型变量的特殊规则
public class C12D1 {
public static volatile int race = 0;
public static void increase() {
race++;
}
private static final int THREADS_COUNT = 20;
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(() -> {
try {
for (int j = 0; j < 10000; j++) {
increase();
}
} finally {
countDownLatch.countDown();
}
});
threads[i].start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(race);
}
}
-------
36654
volatile boolean shutdownRequested;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// 代码的业务逻辑
}
}
https://gee.cs.oswego.edu/dl/jmm/cookbook.html
volatile 的作用?
保证可见性,禁止指令重排序。不保证原子性,例如自增的操作不采用同步不能保证结果正确。
12.3.4 针对long和double型变量的特殊规则
在目前主流平台下商用64位虚拟机并不会出现非原子性访问行为。
12.3.5 原子性、可见性与有序性
12.3.6 先行发生原则
为什么产生先行发生原则?
通过几条简单规则一揽子解决并发环境下两个操 作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩难懂的定义之中。
如何理解先行
比如说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到。
先行发生=可观测。A先行发生B,A先执行后的结果B可以知道。
换句话说,A先进入一个房间,B站在一面透明的墙后便是先行发生,B站在一面水泥墙后便不是先行发生。
如何理解八条规则
规则一 Program Order Rule:同一个线程,每一步操作均可以被下一步所观测。
规则二 Monitor Lock Rule:同一个锁,解锁的过程先行发生于下一次获取锁的过程。
规则三 Volatile Variable Rule:变量写可以被变量读观测到。
规则四 Thread Start Rule:线程启动可以被线程的其他动作所观测到。
规则五 Thread Termination Rule:线程所有操作都可以被线程终止检测所观测,例如 isAlive,join等方法。
规则六 Thread Interruption Rule:线程执行中断方法可以被线程是否中断方法所观测。
规则七 Finalizer Rule:对象初始化的结果finalize()方法可以观测到。
规则八 Transitivity:B可以观测A,C可以观测B,那么C可以观测A。
如何理解代码清单12-9的例子?
线程A先调用setValue,由于没有先行发生,线程B在调用getValue的时候不确定会返回原值还是新值,产生了线程安全问题,也就是说虽然时间上A比B快,也就是执行顺序是A->B,但是不代表这个操作是 先行发生,也就是可观测的。
建议将先行发生原则改为可观测原则,更好理解。
那么如果一个操作可观测是否是先后执行呢。
答案是否定的,代码清单12-10说明了这一点。
同一个线程遵循可观测原则,但是由于指令重排序,i 和 j 的实际执行顺序并不一定按照代码编写的先后顺序执行,这是由于处理器会在保证正确的前提下进行指令重排序。
一句话总结:如果两个操作不满足可观测原则中的8条那么它就是线程不安全的。
12.4 Java与线程
12.4.1 线程的实现
12.4.2 Java线程调度
12.4.3 状态转换
12.5 Java与协程
12.5.1 内核线程的局限
12.5.3 Java的解决方案
12.6 本章小结
java获取汇编代码
汇总网上结论,一般是输入以下两条指令。
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintAssembly
如果我们只输入 -XX:+PrintAssembly
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Error: VM option 'PrintAssembly' is diagnostic and must be enabled via -XX:+UnlockDiagnosticVMOptions.
当我们输入两条参数后系统提示如下
Could not load hsdis-amd64.dll; library not loadable; PrintAssembly is disabled
37109
Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
下载 hsdis-amd64.dll
openjdk 中的 hsdis