【045】面试被问 LockSupport?这玩意儿比 wait/notify 香 10 倍,王二被骂醒了


在这里插入图片描述


📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌

📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌


零、引入

王二的脸比办公室墙角的拖把还灰。刚上线的线程协作代码崩了,满屏的IllegalMonitorStateException像苍蝇似的撞在眼里 —— 他用wait/notify做线程唤醒,偏偏忘了把wait塞进synchronized块里。领导把键盘拍得震天响:“连 wait 的规矩都记不住,你这 Java 是体育老师教的?”

隔壁工位的哇哥正用牙签剔牙,见王二缩成一团,慢悠悠吐掉牙签:“这不能全怪他,wait/notify 的破规矩比旧社会的礼教还多。” 他往王二屏幕上一点,“早该用 LockSupport 了,这玩意儿是线程阻塞唤醒的‘无冕之王’,比 wait/notify 省心百倍。”

点赞 + 关注,跟着哇哥和王二,用医院叫号的道理把 LockSupport 嚼碎了咽下去,下次再写线程协作,领导都得给你递烟。

在这里插入图片描述

一、王二的 “礼教之坑”:wait/notify 的三条破规矩

在这里插入图片描述

王二写的代码是典型的菜鸟写法 —— 想让线程 A 等线程 B 做完活再醒,结果wait没戴synchronized的 “枷锁”,直接炸了。代码长这样

package cn.tcmeta.locksupport;


import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @date: 2025/12/17 14:12
 * @description: 王二的坑:wait/notify的破规矩没遵守,直接崩了
 * @version: 1.0.0
 */
public class WaitNotifyDisasterSample {

    static void main() throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("工人线程:开始干活...");
            try {
                // 坑1:wait()没在synchronized同步块里,直接抛异常
                WaitNotifyDisasterSample.class.wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("工人线程:被唤醒,接着干活!");
        }, "Worker-Thread");

        worker.start();
        // 主线程等2秒,让工人线程先跑起来
        TimeUnit.SECONDS.sleep(2);
        System.out.println("主线程:通知工人线程可以醒了");

        // 坑2:notify()也没在synchronized同步块里
        WaitNotifyDisasterSample.class.notify();
    }
}

在这里插入图片描述

王二挠着后脑勺,苦着脸说:“我记着 wait 和 notify 得配对,哪想到还要套同步块?”

哇哥把刚泡的枸杞茶往桌上一墩,茶沫子溅了王二一手:“这就是 wait/notify 的三条破规矩,比民国的租界规定还多:

  • 同步块枷锁:wait 和 notify 必须在synchronized修饰的代码块或方法里,不然就抛刚才那破异常 —— 就像进租界得有通行证,没证就抓你;
  • 顺序不能乱:必须先 wait 后 notify,要是先 notify 再 wait,那线程就永远睡死了 —— 相当于你提前喊了 “开饭”,等工人来的时候,饭早凉了;
  • 唤醒随机:notify 唤醒的是等待队列里的任意一个线程,想唤醒指定线程?没门 —— 就像食堂打饭,喊 “下一个”,谁挤在前头算谁的。”

王二听得直皱眉:“这破玩意儿这么多破规矩,就没个省心的办法?”

“有啊,LockSupport,” 哇哥掏出烟盒,在上面画了个简笔画这玩意儿是线程阻塞唤醒的‘无冕之王’,那些破规矩全给你废了。”

二、用 “医院叫号” 讲透 LockSupport:唤醒不用看规矩

在这里插入图片描述
哇哥指着烟盒上的简笔画,开始讲 —— 他最擅长把复杂的东西掰成生活里的事儿:

“你把线程当成医院的病人,wait/notify 是啥?是病人自己蹲在挂号窗口门口等,还得守一堆破规矩:必须站在‘同步块’这个黄线内,护士喊‘notify’的时候,谁离得近谁先上,要是护士先喊了,病人后到,那病人就白等。”

“那 LockSupport 呢?” 王二追问。

“LockSupport 是护士拿着叫号机,” 哇哥一拍桌子,“病人(线程)不用蹲在窗口,找个椅子随便坐(不用同步块),护士(调用 unpark)直接按号喊人,哪怕护士先把号给病人(先 unpark 再 park),病人来了也能直接进去 —— 这就是 LockSupport 的霸道之处。

✔️ LockSupport 核心:park 和 unpark,简单到不像话

LockSupport 是 JUC 里的工具类,核心就两个静态方法,比筷子还好用:

  • LockSupport.park():阻塞当前线程 —— 相当于病人听到 “请稍等”,坐下等着;
  • LockSupport.unpark(Thread t):唤醒指定线程 t—— 相当于护士喊 “3 号病人到 1 号窗口”。

👉 最关键的特性:“许可” 机制(王二记在烟盒背面)

哇哥强调,LockSupport 的核心是 “许可”,就像电影票,一张票只能用一次:

  • 调用unpark,就是给线程发一张 “许可”(最多一张,多发无效);
  • 调用park,就是线程用掉这张 “许可”,如果没有许可,就阻塞等着;
  • 要是先unpark(发了票),再park(用票),线程直接通过,不用等 —— 这就解决了 wait/notify 的顺序问题。

“简单说,unpark 是‘发电影票’,park 是‘凭票入场’,” 哇哥总结,“哪怕票先给了,人后到,照样能进,这比 wait/notify 那套规矩人性多了。”

三、改造代码:LockSupport 让线程唤醒 “随心所欲”

在这里插入图片描述
哥拿过王二的鼠标,把刚才的错误代码改成了 LockSupport 版本,只用了几行,清爽得像刚扫过的院子:

package cn.tcmeta.locksupport;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author: laoren
 * @description: 优化版:LockSupport搞定线程阻塞唤醒,没那么多破规矩
 * @version: 1.0.0
 */
public class LockSupportRescue {

    static void main() throws InterruptedException {
        // 1. 创建工人线程
        Thread workerThread = new Thread(() -> {
            System.out.println("工人线程:开始干活,先等等主线程通知...");

            // 2. 阻塞当前线程(不用同步块!)
            LockSupport.park(); // 相当于“等护士叫号”
            System.out.println("工人线程:被唤醒了,接着干活!");
        }, "Worker-Thread");

        // 启动工人线程
        workerThread.start();

        // 主线程等2秒,确保工人线程先执行到park
        TimeUnit.SECONDS.sleep(2);
        System.out.println("主线程:通知工人线程,可以醒了!");

        // 3. 唤醒指定的工人线程(不用同步块!)
        LockSupport.unpark(workerThread); // 相当于“护士喊号”
    }
}

在这里插入图片描述
王二盯着屏幕,眼睛都直了:“真没加同步块!也没抛异常!这也太香了吧?”

“这还不算啥,” 哇哥又改了改代码,把 unpark 放在 park 前面,“你看这个,先唤醒再阻塞,照样能通 —— 这是 wait/notify 打死都做不到的。”

✅ 进阶:先 unpark 再 park,照样能唤醒

package cn.tcmeta.locksupport;


import java.util.concurrent.locks.LockSupport;

import static java.lang.Thread.sleep;

/**
 * @author: laoren
 * @description: // 亮点:unpark在park前面,线程照样能唤醒
 * @version: 1.0.0
 */
public class LockSupportOrderSample {

    static void main() {
        Thread worker = new Thread(() -> {
            System.out.println("工人线程:我先歇1秒,再等通知...");
            try {
                sleep(1000); // 故意让主线程先unpark
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            // 此时主线程已经unpark过了,park直接通过
            LockSupport.park();
            System.out.println("工人线程:居然被唤醒了!先unpark真有用!");

        });

        worker.start();
        // 主线程立即unpark(比worker的park早1秒)
        LockSupport.unpark(worker);
        System.out.println("主线程:先把唤醒通知发出去!");
    }
}

在这里插入图片描述
王二拍腿叫好:“之前用 wait/notify,要是先 notify 再 wait,线程就死等了,LockSupport 这波直接把顺序问题干没了!”

四、LockSupport 核心特性:王二的 “避坑指南”

在这里插入图片描述
哇哥怕王二记混,把 LockSupport 的核心特性写在便利贴上,贴在他显示器正中央:

  • 无需同步块:park 和 unpark 不用在 synchronized 里,彻底摆脱 wait/notify 的枷锁 —— 就像病人不用蹲在窗口等;
  • 许可机制:unpark 是 “发许可”,park 是 “用许可”,许可最多 1 个,多发无效 —— 像电影票,一张够用;
  • 唤醒优先于等待:先 unpark 再 park,线程不会阻塞,直接通过 —— 解决了 wait/notify 的顺序陷阱;
  • 唤醒指定线程:unpark 能精准唤醒某个线程,不像 notify 那样随机唤醒 —— 护士按号喊人,精准定位;
  • 响应中断:park 会响应线程中断,但不会抛出 InterruptedException,需要自己判断线程中断状态 —— 就像病人等的时候被打扰,会醒过来,但不会闹脾气(抛异常)。

📌 park 响应中断

package cn.tcmeta.locksupport;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author: laoren
 * @description: // LockSupport.park()响应线程中断
 * @version: 1.0.0
 */
public class LockSupportInterruptSample {
    static void main() throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("工人线程:开始等待,要是被中断了我就醒...");
            LockSupport.park(); // 阻塞等待

            // 判断是否被中断(park响应中断后,中断标志会被清除吗?不会!)
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("工人线程:被中断唤醒了,中断标志是" + Thread.currentThread().isInterrupted());
            }
        }, "Worker-Thread");

        worker.start();

        // 主线程等2秒,然后中断工人线程
        TimeUnit.SECONDS.sleep(2);
        worker.interrupt();
        System.out.println("主线程:中断工人线程!");
    }
}

在这里插入图片描述

哇哥补充:“这里要注意,park 响应中断后,线程的中断标志还是 true,不像 wait 会清除标志 —— 这是个坑,得自己判断中断状态,不然容易出问题。”

五、面试高频题:LockSupport 必问(附答案)

哇哥整理了 3 道面试必考题,王二抄在小本本上,背得滚瓜烂熟:

💯 面试题 1:LockSupport 和 wait/notify 的核心区别是什么?

这题就用医院叫号的例子答,面试官一听就懂:

  • 同步块要求:wait/notify 必须在 synchronized 里,LockSupport 不用 —— 前者病人要蹲黄线内,后者随便坐;
  • 顺序问题:wait 必须在 notify 之前,否则线程永等;LockSupport 先 unpark 再 park 也能唤醒 —— 前者护士先喊人再等病人,病人白来;后者护士先发卡,病人来了直接进;
  • 唤醒精度:notify 随机唤醒一个线程,notifyAll 唤醒所有;LockSupport 精准唤醒指定线程 —— 前者喊 “下一个”,后者喊 “3 号病人”;
  • 异常处理:wait 会抛 InterruptedException,LockSupport 不会,需自己判断中断状态。

👉 面试题 2:LockSupport 的 park 和 unpark 是怎么实现的?“许可” 是什么?

  • 实现原理:底层调用了 Unsafe 类的park和unpark native 方法,直接和操作系统的线程调度交互;
  • 许可机制:许可就是线程的 “通行凭证”,是一个布尔值(不是计数器):
    • unpark:给线程设置许可为 true(最多 1 个,重复调用许可还是 true);
    • park:检查许可,若为 true 则消费许可(设为 false),线程继续执行;若为 false 则阻塞。

❓ 面试题 3:为什么 LockSupport 的 unpark 可以在 park 之前调用?

因为许可机制是 “先发卡,后检票”——unpark 提前把许可发给线程,线程执行到 park 时,发现有许可就直接消费,不用阻塞;而 wait/notify 是 “先喊人,再等人”,要是先喊人(notify),人还没来(wait),喊人的信号就丢了,人来了只能一直等。

六、总结:LockSupport 核心心法(王二编的顺口溜)

在这里插入图片描述

王二把核心知识点编成顺口溜,贴在键盘上,生怕再忘:

  • wait/notify 规矩多,LockSupport 全挣脱;
  • park 阻塞 unpark 唤,不用同步块里面;
  • 许可机制是核心,一张门票管进门;
  • 先 unpark 再 park,线程照样不耽误;
  • 中断响应不抛错,状态自己要检测。

⚠️ 哇哥的临别赠言

关注我,下一篇咱们扒 LockSupport 的实战大招 —— 用它实现生产者消费者模式,比 Condition 还简洁!还有 AQS 源码里的 LockSupport,原来 ReentrantLock 的阻塞唤醒全靠它,让你从实战到源码,把 LockSupport 啃得干干净净!

在这里插入图片描述

<think>好的,用户想了解JavaLockSupport和Object.wait的区别、关系以及使用场景。首先,我需要回忆这两个机制的基本概念。LockSupport是JUC包中的工具类,提供park和unpark方法用于线程阻塞和唤。而Object.waitnotify是Object类的方法,用于传统的线程间通信,必须在同步块中使用。 接下来,要比较它们的区别。首先想到的是使用方式的不同:Object.wait需要在synchronized块内获取对象锁,否则会抛出IllegalMonitorStateException,而LockSupport.park不需要,直接调用即可。然后是唤机制,wait需要notifynotifyAll来唤,而LockSupport.unpark可以指定线程提前唤,或者之后解除阻塞。还有许可证机制,LockSupport的unpark会给线程一个许可证,即使之后调用park也不会阻塞,这点和wait不同,后者没有这种累积效应。 再者,需要考虑它们的使用场景。Object.wait适用于经典的等待-通知模式,比如生产者消费者题。而LockSupport更适合更底层的线程控制,比如在AQS(AbstractQueuedSynchronizer)中的使用,或者需要更灵活的线程挂起和恢复的场景。 还要提到中断处理。两者对中断的响应不同,park会立即返回并清除中断状态,而wait被中断时会抛出InterruptedException,需要捕获处理。 另外,需要引用提供的参考资料来支持这些观点。例如,引用[4]指出wait必须在synchronized中使用,否则会抛出异常。引用[5]提到三种让线程休眠的方法,包括LockSupport.park。引用[3]和[4]讨论了LockSupportwait的区别,比如许可证机制和同步要求。 最后,应该总结它们的适用情况,帮助用户根据具体需求选择合适的方法。可能用户在实际开发中遇到了线程同步的题,或者在阅读源码时看到这两种方法的使用,需要理解其差异。需要确保回答结构清晰,分点说明,并结合示例代码帮助理解。</think>### JavaLockSupport与Object.wait的关系及区别分析 #### 一、核心关系 两者均为Java线程同步机制,但属于不同层级的解决方案: - $Object.wait()$ 是Java基础API,需要配合$synchronized$关键字使用[^4] - $LockSupport$ 是JUC包中的高级工具,提供更底层的线程控制能力[^3] #### 二、主要差异对比(表格形式) | 特性 | LockSupport | Object.wait/notify | |---------------------|----------------------------|----------------------------| | 同步机制 | 无锁机制 | 必须持有对象监视器锁[^4] | | 唤顺序 | 精准唤指定线程 | 随机唤等待队列中的线程 | | 许可证机制 | 支持许可凭证累积(unpark) | 无类似机制 | | 异常处理 | 无检查异常 | 需处理InterruptedException| | 使用场景 | 并发框架底层实现 | 常规线程间通信 | #### 三、实现原理详解 1. **Object.wait() 工作流程**: ```java synchronized(obj) { // 必须获取对象锁[^2] while(condition) { obj.wait(); // 释放锁并进入等待队列 } // 被唤后自动重新获取锁 } ``` 2. **LockSupport 工作机制**: ```java LockSupport.unpark(targetThread); // 先发放许可 LockSupport.park(); // 有许可立即返回,无许可阻塞[^3] ``` #### 四、典型应用场景 1. **Object.wait适用场景**: - 生产者消费者模式 - 条件变量等待(需配合synchronized使用) - 经典线程间通信场景 2. **LockSupport优势场景**: - 实现高级同步器(如AQS) - 需要先唤后阻塞的特殊逻辑 - 避免锁竞争引发的性能题 - 精确控制特定线程状态 #### 五、中断处理差异 - $LockSupport.park()$响应中断时**直接返回**,需通过$Thread.interrupted()$检测中断状态[^5] - $Object.wait()$被中断会**抛出InterruptedException**,需通过try-catch处理[^1] #### 六、注意事项 1. 错误使用示例: ```java Object o = new Object(); o.wait(); // 抛出IllegalMonitorStateException[^2] ``` 2. 正确配合使用方式: ```java Thread target = new Thread(() -> { LockSupport.park(); System.out.println("被唤"); }); target.start(); LockSupport.unpark(target); // 提前发放许可保证立即执行 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值