竞态条件 (race condition)
12.4.1 竞态条件的一个例子
12.4.2 竞态条件详解

从上图可以看出寄存器和内存之间的操作是多步骤的, 多个业务逻辑会穿插.

12.4.3 锁对象
两种机制防止并发访问代码块.
- ReentrantLock
- synchronized

使用例子:

lock() 不能写在 try 中, 获取锁发生异常时会执行 finally, 那么释放锁时会异常.

重入锁, 线程可以反复获得已拥有的锁.
12.4.4 条件对象

package chapter12;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;public class Bank {private ReentrantLock bankLock = new ReentrantLock();private Condition sufficientFunds;public Bank() {sufficientFunds = bankLock.newCondition();}public void transfer(int from, int to, int amount) {System.out.println("out");try {System.out.println("try");// 被唤醒时再次检测条件while ((int)(Math.random()*10) % 2 == 0) {sufficientFunds.await();}// 其他业务代码// 唤醒其他线程sufficientFunds.signalAll();}catch (Exception e) {System.out.println(e);}finally {System.out.println("finally");}}public static void main(String[] args) {Bank b = new Bank();b.transfer(1, 2, 3);}}
12.4.5 synchronized 关键字
Java 中每个对象都有一个内部锁, 如果方法声明时有 synchronized 关键字, 那么对象的锁将保护整个方法.
public synchronized void method() {method body}// 等价于public void method() {this.intrinsicLock.lock();try {method body}finally {this.intrinsicLock.unlock();}}
使用对象内部锁的条件锁:
waitnotifyAll/notify
public synchronized void transfer2() throws InterruptedException {// 被唤醒时再次检测条件while ((int)(Math.random()*10) % 2 == 0) {wait();}// 其他业务代码// 唤醒其他线程notifyAll();}
内部锁和条件存在限制:

使用哪种锁:

12.4.6 同步块
同步块使用 obj 的锁:
- 客户端锁定, 不推荐使用
应该就是使用每个对象的内部锁?
synchronized (obj) {critical section}
private Object lock = new Object();public void transfer3() {synchronized (lock) {// ...}}

12.4.7 监视器概念
不考虑显式锁就可以保证多线程的安全性.
监视器的特性:

Java 对象在一下3个重要方面不同于监视器:

12.4.8 volatile 字段

- cpu 有多级缓存, 可能指的是缓存于内存的不一致
- 编译器认为改变代码顺序没问题, 但是其他线程可能会更改值
volatile 关键字为实例字段的同步访问提供免锁机制, 编译器会插入适当代码, 确保一个线程对 done 变量做修改时, 这个修改对读取这个变量的其他线程可见.


12.4.9 final 变量
final var accounts = new HashMap<String, Double>();

12.4.10 原子性
猜测: 锁是应用层的, 开销大.
compareAndSet()
12.4.11 死锁

12.4.12 线程局部变量
线程内共享数据.

12.4.13 为什么废弃 stop 和 suspend 方法
- stop 天生就不安全
- stop 在终止线程后会释放被锁定的对象, 导致中间状态被暴露
- suspend 经常导致死锁
- 将获得锁的线程挂起, 那么其他线程获取同一个锁时会死锁
