面试题:3种线程阻塞唤醒的对比

3种线程阻塞唤醒

wait/notify

/**
 * @author WGR
 * @create 2020/12/29 -- 0:28
 */
public class Test6 {

    private static Object objectLock = new Object();

    public static void main(String[] args) {
        new Thread(() ->{
            synchronized (objectLock){
                System.out.println(Thread.currentThread().getName()+"----come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"----被唤醒");
            }
        },"A").start();

        new Thread(() ->{
            synchronized (objectLock){
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"----通知");
            }
        },"B").start();
    }
}

image-20201229003423631

当A先睡眠的时候,B先去唤醒

image-20201229003540263

当去掉锁的代码块的时候

image-20201229003713232

image-20201229003910596

  • Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
  • BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
  • BLOCKED 线程会在 Owner 线程释放锁时唤醒
  • WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争

await/signal

它的演示结果和上面一样就不贴出来了

public class Test6 {

    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {


        new Thread(() ->{
            lock.lock();
               try {
                   try {
                       System.out.println(Thread.currentThread().getName()+"----come in");
                       condition.await();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"----被唤醒");
               }finally {
                   lock.unlock();
               }

        },"A").start();

        new Thread(() ->{
            lock.lock();
            try{
                condition.signal();
                System.out.println(Thread.currentThread().getName()+"----通知");
            }finally {
                lock.unlock();
            }

        },"B").start();
    }
}

await 流程
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject,开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程,创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部

image-20201229004156847

接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁

image-20201229004213744

unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功

image-20201229004234391

park 阻塞 Thread-0

image-20201229004253020

signal 流程
假设 Thread-1 要来唤醒 Thread-0

image-20201229004346041

进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node

image-20201229004416130

执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1

image-20201229004445201

park/unpark

/**
 * @author WGR
 * @create 2020/12/29 -- 1:02
 */
public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("park...");
            LockSupport.park();
            System.out.println("resume...");
        },"t1");
        t1.start();
        System.out.println("unpark...");
        LockSupport.unpark(t1);
    }
}

image-20201229010441291

特点

  • wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必
  • park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】
  • park & unpark 可以先 unpark,而 wait & notify 不能先 notify

image-20201229010815103

  1. 当前线程调用 Unsafe.park() 方法
  2. 检查 _counter ,本情况为 0,这时,获得 _mutex 互斥锁
  3. 线程进入 _cond 条件变量阻塞
  4. 设置 _counter = 0

image-20201229010840158

  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 唤醒 _cond 条件变量中的 Thread_0
  3. Thread_0 恢复运行
  4. 设置 _counter 为 0

image-20201229010859017

  1. 调用 Unsafe.unpark(Thread_0) 方法,设置 _counter 为 1
  2. 当前线程调用 Unsafe.park() 方法
  3. 检查 _counter ,本情况为 1,这时线程无需阻塞,继续运行
  4. 设置 _counter 为 0

常见面试题

image-20201228225823838

posted @ 2020-12-29 01:12  天宇轩-王  阅读(164)  评论(0编辑  收藏  举报