竞态条件 (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();
}
}
使用对象内部锁的条件锁:
wait
notifyAll/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 经常导致死锁
- 将获得锁的线程挂起, 那么其他线程获取同一个锁时会死锁