并发2️⃣Java线程②wait/notify、park/unpark、状态转换

1、wait/notify

背景知识synchronizedMonitor

1.1、API 介绍

  • Object 类提供的方法。
  • 必须成为对象 Monitor 的 Owner,才能调用这两个方法。

1.1.1、wait()

使 Monitor 的 Owner 进入 WaitSet

  • wait(long):最长等待指定毫秒数。

  • wait(long, int):最长等待指定毫秒数 + 1。

  • wait():无限期等待。

    public final native void wait(long timeout) throws InterruptedException;
    
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                "nanosecond timeout value out of range");
        }
        if (nanos > 0) {
            timeout++;
        }
        wait(timeout);
    }
    
    public final void wait() throws InterruptedException {
        wait(0);
    }
    

1.1.2、notify()

唤醒 WaitSet 中的等待线程。

  • notify():随机唤醒一个。

  • notifyAll():唤醒所有。

    public final native void notify();
    public final native void notifyAll();
    

1.2、原理

  1. Monitor 的 Owner 的执行条件不满足时,调用 wait()

    • 主动释放锁。
    • 进入 WaitSet,变成 WAITING 状态。
  2. 此时其它线程可以成为竞争锁,成为新的 Owner

  3. 新的 Owner 调用 notify() 或 notifyAll() 时,唤醒 WaitSet 中的线程。

Waitset 和 EntryList 的区别

Waitset EntryList
线程状态 WAITING BLOCKED
进入条件 已获得锁的线程,由于条件不满足而调用 wait() 尝试获得锁时,由于锁已被其它线程获取而阻塞
唤醒时机 Owner 调用 notify()notifyAll() Owner 释放锁
注意 唤醒后不会直接成为 Owner,而是进入 EntryList 竞争锁 若有多个等待线程,则竞争锁(非公平)

2、park/unpark

2.1、API 介绍

JUC 包下 LockSupport 提供的方法。

// “暂停”当前线程
public static void park();
// “恢复”某个线程
public static void unpark(Thread thread);

2.1.1、park()

调用 park() 时,判断当前线程是否拥有许可

  • 若线程拥有许可,则消耗许可,不会禁用线程。
  • 若线程没有许可,则禁用当前线程。

何时取消禁用?

  1. 其它线程调用 unpark(),当前线程解除阻塞
  2. 其它线程调用 interrupt()中断当前线程。

2.1.2、unpark()

调用 unpark(t),为指定线程 t 添加许可

  • 若线程 t 由于 park() 而阻塞,则解除线程 t 的阻塞。
  • 若线程 t 正在运行,则添加许可,线程 t 下次调用 park() 时将不会阻塞。
  • 若线程 t 未启动,此方法未必生效。

2.2、原理

每个线程独有一个 Parker 对象,由三部分组成

  • _counter:计数器,取值 0/1。
  • _cond:条件变量,当满足条件时解除阻塞。
  • -mutex

调用 park() 时:判断 _counter 的值

  • 若线程拥有许可(值为 1),则消耗许可(值重置为 0),不会禁用线程。
  • 若线程没有许可(值为 0),则禁用当前线程,并进入 _cond 阻塞。
    • 其它线程调用 unpark(),当前线程解除阻塞
    • 其它线程调用 interrupt()中断当前线程。

调用 unpark(t) 时:为指定线程 t 添加许可_counter 设为 1)

  • 若线程 t 由于 park() 而阻塞(处于 _cond),则设置 _counter=1,解除线程 t 的阻塞并重置 counter=0
  • 若线程 t 正在运行,则添加许可(counter=1),线程 t 下次调用 park() 时将不会阻塞。

2.3、方法对比

sleep() wait() park()
API Thread Object LockSupport
synchronized? 不需要强制配合 synchronized 使用 只能在 synchronized 代码块中使用 不需要强制配合 synchronized 使用
释放对象锁?

3、线程状态转换

img

3.1、NEW → RUNNABLE

  • 实例化线程对象时:线程状态为 NEW
  • 调用 start() 时:线程状态 NEWRUNNABLE

3.2、RUNNABLE <--> 阻塞

阻塞状态细分

  • BLOCKED:等待 Monitor 锁。
  • WAITING:无限期等待另一个线程执行完某个特定操作。
  • TIMED_WAITING:在指定等待时间内,等待另一个线程执行完某个特定操作。

3.2.1、WAITING

不会被 CPU 分配时间片,无限期等待显式唤醒。

  • join()
  • wait()
  • park()

join()

  1. 线程 t1 调用 t1.join():t1 进入 t2 对象的 Monitor 的 WaitSet,t1 状态 RUNNABLEWAITING
  2. 唤醒其它线程调用 t1.interrupt()t2 运行结束:t1 退出 WaitSet,状态 WAITINGRUNNABLE

wait()

线程 t 执行到 synchronized(obj){}获取对象锁,成为 Monitor 的 Owner 后:

  1. 线程 t 调用 obj.wait():t 进入 WaitSet,状态 RUNNABLEWAITING
  2. 唤醒其它线程调用 obj.notify()obj.notifyAll()t.interrupt():t 退出 WaitSet,进入 EntryList 竞争锁。
    • 竞争锁成功WAITINGRUNNABLE
    • 竞争锁失败WAITINGBLOCKED

park()

  1. 线程 t 调用 LockSupport.park():t 状态 RUNNABLEWAITING
  2. 唤醒其它线程调用 LockSupport.unpark()t.interrupt():t 状态 WAITINGRUNNABLE

3.2.2、TIMED_WAITING

不会被 CPU 分配时间片,等待显式唤醒。

相比 WAITING,等待超时后自动唤醒

  • sleep(long)
  • 对应 join()wait()park() 的超时等待方法。

sleep(long)

  1. 线程 t 调用 Thread.sleep(long),状态 RUNNABLETIMED_WAITING
  2. 唤醒t1.interrupt() 显式唤醒、等待超时自动唤醒,状态 TIMED_WAITINGRUNNABLE

join(long)

  1. 线程 t1 调用 t1.join(long):t1 进入 t2 对象的 Monitor 的 WaitSet,t1 状态 RUNNABLETIMED_WAITING
    注意是当前线程在t 线程对象的监视器上等待
  2. 唤醒:状态 TIMED_WAITINGRUNNABLE
    1. 显式唤醒:其他线程调用t1.interrupt()、t2 运行结束
    2. 自动唤醒:t1 等待超时

wait(long)

线程 t 执行到 synchronized(obj){}获取对象锁,成为 Monitor 的 Owner 后:

  1. 线程 t 调用 obj.wait(long):t 进入 WaitSet,状态 RUNNABLETIMED_WAITING
  2. 唤醒:t 退出 WaitSet,进入 EntryList 竞争锁。
    • 类型
      • 显式唤醒:其它线程调用 obj.notify()obj.notifyAll()t.interrupt()
      • 自动唤醒:线程 t 等待超时。
    • 竞争锁
      • 竞争锁成功TIMED_WAITINGRUNNABLE
      • 竞争锁失败TIMED_WAITINGBLOCKED

parkXxx(long)

  1. 线程 t 调用 LockSupport.parkNanos(long)LockSupport.parkUntil(long):t 状态 RUNNABLETIMED_WAITING
  2. 唤醒:t 状态 TIMED_WAITINGRUNNABLE
    1. 显式唤醒:其它线程调用 LockSupport.unpark()t.interrupt()
    2. 自动唤醒:线程 t 等待超时。

3.2.3、BLOCKED

线程 t 竞争锁失败,状态 RUNNABLE → BLOCKED

  • t 进入 synchronized(obj){...} 竞争锁失败:进入 EntryList,状态 RUNNABLEBLOCKED
  • Monitor 的 Owner 执行结束,唤醒 EntryList 中所有阻塞线程进行锁竞争。
    • 竞争成功BLOCKEDRUNNABLE
    • 竞争失败BLOCKEDRUNNABLEBLOCKED

3.3、RUNNABLE → TERMINATED

线程 t 代码执行结束:RUNNABLE TERMINATED

posted @ 2022-03-23 19:19  Jaywee  阅读(60)  评论(0编辑  收藏  举报

👇