并发理论总结1-并发源头和解决并发问题的方案
并发bug源头
可见性,原子性,有序性。
可见性
问题本源:缓存
多核时代,每个CPU都有自己的缓存,当多个线程在不同的CPU上执行时,这些线程的数据存在不同的CPU缓存中,这也就导致了并发的可见性问题。
原子性
问题本源:线程切换 或者说 cpu的原子性是cpu指令。
JAVA的原子性是指一个线程在执行过程中不被中断,但是CPU的原子性是cpu指令,所以像JAVA这种高级语言的执行实际在CPU中执行,不光是从上往下执行,还会发生线程切换。
有序性
问题本源:JVM编译优化。
JVM编译字节码文件的时候,会优化性能的,有时候就会改变程序中语句的执行顺序。
解决可见性和有序性
粗暴的解决:直接禁用CPU缓存和编译优化。
优雅的解决:按需禁用,最大程度的保持代码的性能优异性。
解决可见性
JVM内存模型对可见性提出了解决的理论依据
Happends-Before规则
这个规则的核心是:前面操作的结果对于后续操作时可见的。
1.程序的顺序性规则
同一个线程中,程序按照自上而下的顺序执行,程序前面某个变量的修改对于后续操作时可见的。
2.volatile变量规则
对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。
notice:
volatile 关键字并不是 Java 语言的特产,古老的 C 语言里也有,它最原始的意义就是禁用 CPU 缓存。告诉编译器,对这个变量的读写,不能使用 CPU 缓存,必须从内存中读取或者写入。
3.传递性规则
如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
4.管程中的锁规则
对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
notice:
管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现。
5.线程start()规则
主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。
6.线程join()规则
主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。所谓的“看到”,指的是对共享变量的操作。
以上是happens-before规则中常用到的规则。
实际应用中的解决方案
实际中更多地是用锁保护共享变量,来禁止CPU缓存,来解决可见性,切符合happens-before规则,释放锁对于获取锁是可见的。简单粗暴又好用。
解决有序性
通过volitale修饰变量,禁止jvm编译优化。
volatile boolean v = false;
notice:
和volitale相对应的是final,final修饰变量就是告诉编译器,这个变量生儿不变,可劲优化。
解决原子性
禁止线程切换,通过互斥锁来实现,保证在CPU中同一时刻只有一个线程对共享变量进行操作。
还没有评论,来说两句吧...