竞态条件 (race condition)

12.4.1 竞态条件的一个例子

12.4.2 竞态条件详解

image.png

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

image.png

12.4.3 锁对象

两种机制防止并发访问代码块.

  • ReentrantLock
  • synchronized

image.png

使用例子:

image.png

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

image.png

重入锁, 线程可以反复获得已拥有的锁.

12.4.4 条件对象

image.png

  1. package chapter12;
  2. import java.util.concurrent.locks.Condition;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class Bank {
  5. private ReentrantLock bankLock = new ReentrantLock();
  6. private Condition sufficientFunds;
  7. public Bank() {
  8. sufficientFunds = bankLock.newCondition();
  9. }
  10. public void transfer(int from, int to, int amount) {
  11. System.out.println("out");
  12. try {
  13. System.out.println("try");
  14. // 被唤醒时再次检测条件
  15. while ((int)(Math.random()*10) % 2 == 0) {
  16. sufficientFunds.await();
  17. }
  18. // 其他业务代码
  19. // 唤醒其他线程
  20. sufficientFunds.signalAll();
  21. }
  22. catch (Exception e) {
  23. System.out.println(e);
  24. }
  25. finally {
  26. System.out.println("finally");
  27. }
  28. }
  29. public static void main(String[] args) {
  30. Bank b = new Bank();
  31. b.transfer(1, 2, 3);
  32. }
  33. }

12.4.5 synchronized 关键字

Java 中每个对象都有一个内部锁, 如果方法声明时有 synchronized 关键字, 那么对象的锁将保护整个方法.

  1. public synchronized void method() {
  2. method body
  3. }
  4. // 等价于
  5. public void method() {
  6. this.intrinsicLock.lock();
  7. try {
  8. method body
  9. }
  10. finally {
  11. this.intrinsicLock.unlock();
  12. }
  13. }

使用对象内部锁的条件锁:

  1. wait
  2. notifyAll/notify
  1. public synchronized void transfer2() throws InterruptedException {
  2. // 被唤醒时再次检测条件
  3. while ((int)(Math.random()*10) % 2 == 0) {
  4. wait();
  5. }
  6. // 其他业务代码
  7. // 唤醒其他线程
  8. notifyAll();
  9. }

内部锁和条件存在限制:

image.png

使用哪种锁:

image.png

12.4.6 同步块

同步块使用 obj 的锁:

  • 客户端锁定, 不推荐使用

应该就是使用每个对象的内部锁?

  1. synchronized (obj) {
  2. critical section
  3. }
  1. private Object lock = new Object();
  2. public void transfer3() {
  3. synchronized (lock) {
  4. // ...
  5. }
  6. }

image.png

12.4.7 监视器概念

不考虑显式锁就可以保证多线程的安全性.

监视器的特性:

image.png

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

image.png

12.4.8 volatile 字段

image.png

  • cpu 有多级缓存, 可能指的是缓存于内存的不一致
  • 编译器认为改变代码顺序没问题, 但是其他线程可能会更改值

volatile 关键字为实例字段的同步访问提供免锁机制, 编译器会插入适当代码, 确保一个线程对 done 变量做修改时, 这个修改对读取这个变量的其他线程可见.

image.png

image.png

12.4.9 final 变量

  1. final var accounts = new HashMap<String, Double>();

image.png

12.4.10 原子性

猜测: 锁是应用层的, 开销大.

compareAndSet()

12.4.11 死锁

image.png

12.4.12 线程局部变量

线程内共享数据.

image.png

12.4.13 为什么废弃 stop 和 suspend 方法

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