日常学习笔记
会的越多,不会的越多
戒浮戒躁,脚踏实地
笔记
并发编程的三个问题
原子性 -> 一个操作是不可中断的,要么全部执行成功要么全部执行失败。
指令级别语义:CPU单个指令一定是原子性的。
java语言语义:java中一个指令不代表是具备原子性的。java指令是对CPU指令的封装。(1 - n)
有序性 -> 程序按照代码顺序有序执行
编译期优化:在java编译期,JVM认为改变指令顺序不会影响结果的场景中(不违反happens-before),会进行编译期的指令重排
CPU指令重排:为了解决MESI协议导致的CPU空闲,引入了指令重排机制。大大提高了CPU的利用率
可见性 -> 当前线程对共享变量的修改,对其它线程立即可见
JMM语义:在JMM中线程对共享变量的修改对其它线程立即可见。
CPU语义:一个内核对L1/l2缓存的M(modify)操作对其它S(share)该变量的内核可见。
可见性问题的根本来源:指令重排导致的CPU指令乱序执行。最终根源
Store Buffere
Invalidte Queue
金句
在采用一项技术的同时,一定要清楚它带来的问题是什么,以及如何规避
举个例子,我们为了对系统实施监控,会引入例如pinpoint之类的AMP组件,解决了监控问题
的同时也带来了性能问题,比如对带宽的占用,增加了接口响应的延时等;再比如,微服务
架构是为了解决单体应用灵活性差等问题而出现,同时也带了了架构的复杂度,增加了服务
之间通信,数据隔离等问题。所以,一个技术在解决某个问题的同时可能带来新的问题,这样
我们可能又会为新的问题引入别的技术来处理,这是个不断循环的过程。因此,我们在评估
一项技术的时候,需要充分考虑其负面影响,怎么权衡利弊,实现利益最大化。
CPU --> 三级缓存 --> MESI协议 --> 指令重排
笔记
java内存模型
JMM是JVM兼容不同的CPU架构的基础。为了屏蔽底层硬件的差异,向开发者提供统一的接口,故诞生了JMM
JMM只是规范,JMM只是规范,JMM只是规范
JVM对JMM的实现,才是常见的堆、栈、方法区等一些耳熟能详的名词
可见性、有序性的根本解决方案
程序员:对CPU缓存、编译器等按需禁用缓存以及编译优化
方法:volatile/synchronized/final
以上三种方法是java提供给程序员“按需”禁止缓存及编译优化的手段。
JVM:happens-before原则
在JVM可预见的场景中禁止CPU缓存、编译器优化
程序次序规则
在一个线程中,前面的操作总是对后面操作可见
锁定规则
一个unlock操作先行发生于后面对这个锁的lock操作(先释放,才能加锁)
传递性规则
A happens-before B B happens-before C 则 A happens-before C (以前理解不到位)
class VolatileExample { int x = 0; volatile boolean v = false; public void writer() { x = 42; v = true; } public void reader() { if (v == true) { // 这里x会是多少呢? } } }线程start规则
start前的操作,总是对被start的操作可见。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。
Thread B = new Thread(()->{ // 主线程调用B.start()之前 // 所有对共享变量的修改,此处皆可见 // 此例中,var==77 }); // 此处对共享变量var修改 var = 77; // 主线程启动子线程 B.start();线程 join() 规则
这条是关于线程等待的。它是指主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),
当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作。
理解
文中的禁用CPU缓存,更深层次的理解见
store buffer
Invalidate Queue
volatile/synchronized/final等是java提供给程序员禁止指令重排和禁用缓存的工具
CPU为了提高利用率需要指令重排,但是在一些场景中指令重排会导致一些意想不到的错误。这时候需要程序员来发现问题并给出了解决问题的手段
重点理解JMM是一种规范、不能混淆JVM对JMM的实现
03 | 互斥锁(上):解决原子性问题笔记
原子性问题到底该如何解决呢