并发4️⃣管程⑥线程执行顺序控制、管程知识点小结

1、线程执行顺序控制

1.1、说明

执行顺序

  • 固定顺序:如 t1 → t2 → t3
  • 交替执行:如 t1 → t2 → t3t1 → t2 → t3

思路:让线程 m 进入某种等待状态,线程 n 执行后通知线程 m 可以执行。

实现方式

  1. Monitor:wait/notify(join 的本质也是 wait/notify)
  2. ReentrantLock
  3. LockSupport:park/unpark

1.2、示例

案例:要求按 t3、t2、t1 的次序执行。

线程:不加任何控制,先 start() 的线程大概率先执行。

Thread t1 = new Thread(() -> debug("执行"), "t1");
Thread t2 = new Thread(() -> debug("执行"), "t2");
Thread t3 = new Thread(() -> debug("执行"), "t3");

t1.start();
t2.start();
t3.start();

1.2.1、wait/notify

思路

  1. 创建锁对象 LOCK,让每个线程进入 LOCK 所关联 Monitor 的 WaitSet 进行等待。

  2. 定义 2 个标识,作为线程 t1 和 t2 循环条件。

    (本例中线程执行顺序是 t3 -> t2 -> t1。)

    • 当 t3 执行后修改 flag2,使 t2 能够执行。

    • 当 t2 执行后修改 flag1,使 t1 能够执行。

      static final Object LOCK = new Object();
      static boolean FLAG1 = false, FLAG2 = false;
      

实现

  • t1
    1. 循环,获取 LOCK 对象锁并进入 WaitSet(循环条件:FLAG1 为 false)。
    2. 当 FLAG1 == true 跳出循环,执行代码。
  • t2
    1. 循环,获取 LOCK 对象锁并进入 WaitSet(循环条件:FLAG2 为 false)。
    2. 当 FLAG2 == true 跳出循环,执行代码。
    3. 设置 FLAG1 并唤醒 WaitSet 中的阻塞线程。
  • t3:执行代码,设置 FLAG2 并唤醒 WaitSet 中的阻塞线程。

1.2.2、ReentrantLock

相比 Monitor,可以定义多个条件变量。

  1. 创建锁对象 reentantLock,创建 2 个条件变量。
  2. 让每个线程进入对应的条件变量进行等待(await()),直到被唤醒(signalAll()

1.2.3、Park/Unpark

LockSupport

对指定线程进行暂停和恢复,参考 2-park/unpark

2、管程:小结

2.1、共享问题

共享问题:多个线程访问共享变量,且执行过程不具有原子性。

  • 临界区:多线程下,对共享资源执行读写操作的代码块。
  • 发生竞态条件:多个线程执行临界区代码,由于代码的执行序列不同,无法预测结果。

2.2、synchronized

也称对象锁

属于重量级锁、悲观锁。

2.2.1、使用

  • 语法:对象、方法签名。

    synchronized(对象) {
        // 临界区
    }
    
    权限修饰符 synchronized [static] 返回值 方法名() {
        // 内部有临界区代码
    }
    
  • 2.3 - 线程八锁

  • 变量线程安全

    • 位置:成员变量、局部变量。
    • 类型:基本类型、引用类型。

2.2.2、Monitor

Monitor

  • 对象组成:对象头(Mark Word)

    • 32 位虚拟机

      |---------------------------------------------------|--------------------|
      |                 Mark Word (32 bits)               |       State        |
      |---------------------------------------------------|--------------------|
      |      hashcode:25     | age:4 | biased_lock:0 | 01 |       Normal       |
      |---------------------------------------------------|--------------------|
      |  thread:23 | epoch:2 | age:4 | biased_lock:1 | 01 |       Biased       |
      |---------------------------------------------------|--------------------|
      |            ptr_to_lock_record:30             | 00 | Lightweight Locked |
      |---------------------------------------------------|--------------------|
      |          ptr_to_heavyweight_monitor:30       | 10 | Heavyweight Locked |
      |---------------------------------------------------|--------------------|
      |                                              | 11 |   Marked for GC    |
      |---------------------------------------------------|--------------------|
      
    • 64 位虚拟机

      |-------------------------------------------------------|--------------------|
      |                      Mark Word (64 bits)              |       State        |
      |-------------------------------------------------------|--------------------|
      |unused:25|hashcode:31|unused:1|age:4|biased_lock:0| 01 |       Normal       |
      |-------------------------------------------------------|--------------------|
      | thread:54 | epoch:2 |unused:1|age:4|biased_lock:1| 01 |       Biased       |
      |-------------------------------------------------------|--------------------|
      |                 ptr_to_lock_record:62            | 00 | Lightweight Locked |
      |-------------------------------------------------------|--------------------|
      |              ptr_to_heavyweight_monitor:62       | 10 | Heavyweight Locked |
      |-------------------------------------------------------|--------------------|
      |                                                  | 11 |    Marked for GC   |
      |-------------------------------------------------------|--------------------|
      
  • Monitor 结构

    含义 对应线程状态
    owner 锁的持有者,正在执行同步代码块 (同一时刻,Monitor 只能有一个 Owner) RUNNABLE
    EntryList 由于锁已被其它线程获取,尝试获取锁失败的阻塞线程 BLOCKED
    WaitSet 锁的持有者由于条件不满足主动释放锁,进入等待状态 WAITING
  • 字节码分析:monitorenter、monitorexit

2.2.3、wait/notify

wait/notify

  • API
    • wait:无限期等待、超时等待。
    • notify:随机唤醒一个、唤醒所有。
  • 原理:涉及 Monitor 结构的 WaitSet
    1. Owner 线程执行条件不满足时,调用 wait(),主动释放锁并进入 WaitSet,成为 WAITING 状态。
    2. 其它线程可竞争锁,成为新的 Owner
    3. 新的 Owner 调用 notify() 或 notifyAll() 时,唤醒 WaitSet 中的线程。

2.2.4、synchronized 锁优化

synchronized 是重量级锁,并发性能低。

锁优化策略

  • 加锁策略:不加锁 → 偏向锁 → 轻量级锁 → 重量级锁。
  • 其它策略:自旋、锁消除、锁粗化等。

2.3、锁的活跃性

活跃性、ReentrantLock

2.3.1、活跃性

  • 死锁:多个线程尝试获取对方正占有的资源。
  • 饥饿:优先级低的线程一直无法获得 CPU 时间片,但没有结束。

2.3.2、ReentrantLock

支持中断、超时时间、公平锁、多个条件变量。

posted @ 2022-04-17 18:00  Jaywee  阅读(54)  评论(0编辑  收藏  举报

👇