第 1 章 走入并行世界
1.1 何去何从的并行世界
1.2 必须知道的几个概念
1.2.1 同步和异步
1.2.2 并发和并行
这两个比较容易混淆,都可以表示多个任务一起执行,但侧重点不同。
并发侧重多个任务交替执行,并行侧重多个任务同时执行。
并发是一定时间内连续执行多个任务,并行是多个任务同时执行。
1.2.3 临界区
临界区用来表示公共资源或者说共享数据,可以被多个线程使用,但是每次只能有一个线程使用,其他必须等待使用结束。
1.2.4 阻塞和非阻塞
多用来形容多线程间的相互影响。
阻塞:如果占用了临界区的线程不释放资源,其他线程就必须在这个临界区等待。
非阻塞:线程之间不会互相妨碍,所有线程都会不断尝试向前执行。
1.2.5 死锁、饥饿和活锁
属于多线程的活跃性问题。
死锁:线程间互相争用资源,导致永远互相等待。
饥饿:一个或多个线程由于种种原因无法获得所需的资源,导致一直无法执行。
活锁:线程所需资源不断在线程之间跳动,导致没有一个线程可以同时拿到所有资源正常执行。
1.3 并发级别
1.3.1 阻塞
悲观
1.3.2 无饥饿
没有优先级,按顺序进入临界区执行
1.3.3 无障碍
乐观
先执行,有冲突则回滚
一致性标记:操作完成前后分别读取一致性标记,判断是否更改过,有更改重试,无更改更新标记再修改数据。
1.3.4 无锁
无锁和无障碍的不同:无锁的并行总能保证有一个线程可以正常执行,其他线程必须不断重试竞争,可能出现类似饥饿现象。
1.3.5 无等待
1.4 有关并行的两个重要定律
1.4.1 Amdahl 定律
1.4.2 Gustafson 定律
1.4.3 二者侧重点不同
A:串行化比例一定,加速比有上限
G:可被并行化比例越大,加速比就可以随着 CPU 数量线性增长
1.5 回到 Java:JMM
1.5.1 原子性
操作一旦开始,不会被中断
1.5.2 可见性
共享变量修改后,其他线程能够立即知道这个修改。
1.5.3 有序性
可能发生指令重排,但不会改变串行下的语义
1.5.4 Happen-Before 规则
判断是否可以重排
第 2 章 Java 并行程序基础
2.1 有关线程你必须知道的事
-
线程是轻量级的进程,是程序执行的最小单位。
-
线程切换成本比进程切换小。
-
Java 中线程 Thread 的所有状态
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
- 线程生命周期(注意箭头方向)
NEW-(start)->RUNNABLE-(结束)->TERMINATED RUNNABLE<-(synchronized)->BLOCKED RUNNABLE-(wait/join)->WAITING RUNNABLE<-(notify/结束)-WAITING RUNNABLE-(wait/join)->TIMED_WAITING RUNNABLE<-(notify/结束)-TIMED_WAITING
2.2 初始线程:线程的基本操作
2.2.1 新建线程
- 直接
new Thread()
,需要重写run()
方法。 - 或者实现
Runnable
接口,在new Thread()
的时候作为参数传进去。 - 使用线程池
注意:不要使用 run()
方法开启新线程,这样只会在当前线程串行执行 run()
方法中的代码。
package top.ordinaryroad.learn.chapter._2; /** * 线程基本操作 * * @author mjz * @date 2021/8/26 */ public class HelloThread implements Runnable { public static void main(String[] args) { Thread t1 = new Thread() { @Override public void run() { System.out.println("Hello Thread t1"); } }; t1.start(); Thread t2 = new Thread(new HelloThread()); t2.start(); } @Override public void run() { System.out.println("Hello Thread t2"); } }
2.2.2 终止线程
千万不要调用 stop()
方法,这样可能会使数据发生错乱,而且不好排查问题。
可以使用一个布尔类型的变量 stopMe
volatile boolean stopMe = false;
并提供一个将 stopMe
修改为 true
的方法
public void stopMe() { this.stopMe = true; }
在 run()
方法体最开始判断这个变量,如果为 true
则不执行操作或者跳出循环即可。
2.2.3 线程中断
2.2.4 等待 wait
和通知 notify
2.2.5 挂起 suspend
和继续执行 resume
线程
2.2.6 等待线程结束 join
和谦让 yeild
2.3 volatile 与 Java 内存模型(JMM)
2.4 分门别类的管理:线程组
2.5 驻守后台:守护线程(Daemon)
2.6 先做重要的事:线程优先级
2.7 线程安全的概念与关键字 synchronized
2.8 程序中的幽灵:隐蔽的错误
2.8.1 无提示的错误案例
求平均值整形溢出
2.8.2 并发下的 ArrayList
2.8.3 并发下诡异的 HashMap
2.8.4 初学者常见问题:错误的加锁
Integer
是不变对象,使用 synchronized(Integer)
获取到的可能不是同一个对象的锁