并发编程的第一原则:不要写并发程序
多线程并发问题
- 硬件的核心矛盾:CPU 与内存、I/O 的速度差异
- 三个核心问题:分工、同步、互斥
- 分工:如何高效地拆解任务并分配给线程(Fork/Join 框架就是一种分工模式)
- 同步:线程之间如何协作(CountDownLatch 就是一种典型的同步方式)
- 互斥:保证同一时刻只允许一个线程访问共享资源(可重入锁就是一种互斥手段)
- 核心矛盾:CPU、内存、I/O 设备,这三者的速度差异
- 并发编程Bug的源头:
- 缓存导致的可见性问题(volatile解决)
- 线程切换带来的原子性问题(锁解决)
- 编译优化带来的有序性问题(happens-before解决)
Java内存模型
- Java 内存模型规范了 JVM 如何提供按需禁用缓存和编译优化的方法。具体来说,这些方法包括 volatile、synchronized 和 final 三个关键字,以及八项 Happens-Before 规则。
- volatile:最原始的意义就是禁用 CPU 缓存。
- final:这个变量生而不变,可以可劲儿优化。
- happens-before:前面一个操作的结果对后续操作是可见的。
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作 happens-before 书写在后面的操作。
- 锁定规则:一个unLock操作 happens-before 后面对同一个锁的lock操作。
- volatile变量规则:对一个变量的写操作 happens-before 后面对这个变量的读操作。
- 传递规则:如果操作A happens-before 操作B,而操作B又 happens-before 操作C,则可以得出操作A happens-before 操作C。
- 线程启动规则:Thread对象的start()方法 happens-before 此线程的每个一个动作。
- 线程中断规则:对线程interrupt()方法的调用 happens-before 被中断线程的代码检测到中断事件的发生。
- 线程终结规则:线程中所有的操作都 happens-before 线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。
- 对象终结规则:一个对象的初始化完成 happens-before 他的finalize()方法的开始