【049】Thread.yield () 是鸡肋?3 个真相让你颠覆认知

在这里插入图片描述


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

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

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


零、引入

王二的脸涨得像刚蒸好的肉包子,手指在键盘上戳得噼啪响 —— 他写的监控线程占着 CPU 不放,主线程卡得连日志都刷不出来。前几天听人说 Thread.yield () 能 “让线程懂事点”,他抄起就用,结果监控线程是 “让了”,却像放了个空屁,CPU 占用率还是飙到 99%。

“这 yield 到底是啥破烂!说让线程让资源,结果比老赖还顽固!” 王二的吼声惊得窗台上的麻雀扑棱棱飞走。隔壁工位的哇哥正用旧布擦他的搪瓷缸,闻言慢悠悠抬头:“不是 yield 没用,是你把它当救星了 —— 这玩意儿就像车间里的轮岗示意,你举个‘让工位’的牌子,不是让你滚蛋,是让组长重新安排,懂?”

点赞 + 关注,跟着哇哥把 Thread.yield () 的底裤扒干净,3 个真相戳破误区,下次再用这方法,保准比老司机还稳。
在这里插入图片描述

一、王二的 “空屁代码”:把 yield 用成 “摆设”

在这里插入图片描述

王二写的是系统监控程序,一个线程循环采集数据,一个主线程处理数据。他觉得采集线程太 “霸道”,就加了 yield 想让它 “让着点”,结果全白搭。代码如下,透着股急病乱投医的味儿:

package cn.tcmeta.yield;


import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: 王二的坑:把Thread.yield()当“资源释放器”,纯属误解
 * @version: 1.0.0
 */
public class YieldMisuseSample {
    // 采集线程:循环采集系统负载
    private static class CollectThread extends Thread {
        public CollectThread() {
            super("Collect-Thread");
        }

        @Override
        public void run() {
            long count = 0;
            while (!Thread.currentThread().isInterrupted()) {
                // 模拟采集数据(空循环,故意占CPU)
                count++;
                // 王二认为:加yield就能让线程释放CPU
                Thread.yield();
                // 每采集100万次打印一次
                if (count % 1000000 == 0) {
                    System.out.println(this.getName() + ":已采集" + count / 10000 + "万次");
                }
            }
        }
    }

    // 主线程:处理采集到的数据
    static void main() throws InterruptedException {
        CollectThread collectThread = new CollectThread();
        collectThread.start();

        // 主线程处理数据,看是否被阻塞
        long mainCount = 0;
        while (true) {
            mainCount++;
            if (mainCount % 500000 == 0) {
                System.out.println("【主线程】:已处理" + mainCount / 10000 + "万次");
            }
            // 模拟处理耗时
            TimeUnit.MICROSECONDS.sleep(1);
        }
    }
}

运行结果:yield 成了 “空气”,CPU 照样被占满
在这里插入图片描述
王二瘫在椅子上,挠着后脑勺:“明明加了 yield,咋采集线程还这么霸道?”

哇哥把擦干净的搪瓷缸往桌上一墩,发出 “当” 的一声脆响:“你这是把 yield 的意思理解反了!yield 不是‘释放 CPU’,是‘提示操作系统:我这活儿暂时不急,你给其他线程分分时间片’—— 但听不听,得看操作系统脸色,更得看线程优先级。”

他指着代码:“你这采集线程和主线程优先级一样,都是 5。yield 喊完,操作系统可能转头又把时间片分给它 —— 就像车间里你举着‘让工位’的牌子,组长看你和下个人都一样重要,说不定还让你接着干。”

二、用 “车间轮岗” 讲透 yield:是 “让工位” 不是 “滚蛋”

在这里插入图片描述
哇哥拽过王二桌上的草稿纸,画了个歪歪扭扭的车间,三个工人围着两个工位,旁边标着 “CPU 时间片”—— 这是他的拿手好戏,再玄乎的技术,到他手里都能变成车间里的事儿。

“CPU 就像车间里的两个工位,线程就是工人,” 哇哥指着草稿纸,“每个工人轮流用工位,用完一个时间片(比如 10ms)就下来,这叫‘抢占式调度’。那 yield 是啥?是工人刚用了 5ms,发现手里的活儿是循环检查,不急,就举个牌子说‘组长,我这步不急,让别人先上’—— 这就是 yield 的作用:主动放弃当前时间片的剩余部分,提示操作系统重新调度”

**他顿了顿,用铅笔圈出 “提示” 两个字:“重点是‘提示’,不是‘命令’。**操作系统听不听,看两点:一是有没有优先级更高的线程等着 —— 要是有,立马让;二是优先级相同的线程,看调度算法 —— 可能让,也可能不让。”

🔥 yield 核心真相(王二记在烟盒内侧)

王二掏出皱巴巴的烟盒,用铅笔歪歪扭扭地记,字里行间透着恍然大悟:

  • 作用:主动放弃当前 CPU 时间片的剩余部分,将线程状态从 “运行中” 转为 “就绪”,提示操作系统重新调度;
  • 不是放弃资源:线程不会阻塞,也不会释放锁,只是回到就绪队列 “排队”;
  • 调度依赖 OS:优先级相同的线程可能被重新选中,优先级低的线程几乎没机会;
  • 底层原理:调用 Unsafe 类的yield()本地方法,最终由操作系统的线程调度器决定后续调度。

yield 的线程状态变化

在这里插入图片描述

三、yield 的 3 个核心真相:戳破 “鸡肋” 谣言

在这里插入图片描述

王二还是半信半疑:“照这么说,yield 不就是个‘礼貌性让贤’?那它到底有啥用?”

哇哥冷笑一声,拿出三个代码示例,像甩巴掌似的把王二的误解拍碎:“不是 yield 没用,是你用错了地方。这三个真相,能让你把 yield 用出花来。”

➡️ 真相 1:yield 不释放锁,别指望它解决 “锁霸占”

王二曾以为 yield 能让线程释放锁,结果写了段代码,锁照样被占着。这是他的错误代码:

package cn.tcmeta.yield;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: laoren
 * @description: // 错误认知:认为yield能释放锁
 * @version: 1.0.0
 */
public class YieldLockMisuseSample {

    private static final Lock LOCK = new ReentrantLock();
    private static int count = 0;

    static void main() {
        // 线程1:持有锁,调用yield
        Thread thread1 = new Thread(() -> {
            LOCK.lock();
            try {
                while (count < 10) {
                    count++;
                    System.out.println("线程1:count=" + count);
                    // 以为yield能释放锁,让线程2执行
                    Thread.yield();
                    // 模拟耗时操作
                    TimeUnit.MILLISECONDS.sleep(100);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                LOCK.unlock();
            }
        }, "Thread-1");

        // 线程2:等待锁
        Thread thread2 = new Thread(() -> {
            LOCK.lock();
            try {
                System.out.println("线程2:终于拿到锁!count=" + count);
            } finally {
                LOCK.unlock();
            }
        }, "Thread-2");

        thread1.start();
        thread2.start();
    }

}

运行结果:线程 2 一直等,yield 没释放锁
在这里插入图片描述

哇哥指着代码:“看到没?yield 只释放 CPU 时间片,不释放锁 —— 就像工人占着工位还拿着工具,就算举着‘让工位’的牌子,别人也没法用。想释放锁,得用 unlock (),别指望 yield。”

🔥 真相 2:yield 能优化 “忙等”,避免 CPU 空转

这才是 yield 的真正用武之地 —— 当线程在循环中 “忙等” 某个条件(比如等待 flag 变为 true),不用 sleep(睡久了不及时),用 yield 既能让 CPU 给别人用,又能快速响应条件变化。

正确用法:优化忙等场景

package cn.tcmeta.yield;


import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: // yield的正确用法:优化忙等,避免CPU空转
 * @version: 1.0.0
 */
public class YieldBusyWaitFix {

    // 标记:是否有新数据
    private static volatile boolean hasNewData = false;
    // 存储数据
    private static String data = "";

    static void main() {
        // 消费者线程:忙等新数据
        Thread consumer = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                // 忙等条件:有新数据
                while (!hasNewData) {
                    // 关键:用yield避免CPU空转,主动让时间片
                    Thread.yield();
                }
                // 处理数据
                System.out.println("消费者:拿到数据=" + data);
                // 重置标记
                hasNewData = false;
            }
        }, "Consumer-Thread");
        consumer.start();

        // 生产者线程:间隔产生数据
        Thread producer = new Thread(() -> {
            try {
                for (int i = 0; i < 5; i++) {
                    TimeUnit.SECONDS.sleep(2);
                    data = "新数据-" + i;
                    hasNewData = true;
                    System.out.println("生产者:产生数据=" + data);
                }
                // 中断消费者,结束程序
                consumer.interrupt();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }, "Producer-Thread");
        producer.start();
    }
}

运行结果:CPU 不空转,响应及时

在这里插入图片描述

哇哥解释:“要是不用 yield,消费者线程会在 while (!hasNewData) 里疯狂循环,把 CPU 占满;加了 yield,它每次循环都主动让时间片,CPU 占用率能从 99% 降到 1% 以下 —— 这才是 yield 的真本事。”

✔️ 真相 3:yield 和 sleep 的区别,别再搞混

王二总把 yield 和 sleep 弄混,哇哥干脆画了张表,像私塾先生罚抄似的让他背:
在这里插入图片描述
对比代码:yield 和 sleep 的调度差异

package cn.tcmeta.yield;


import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: // yield vs sleep 调度差异
 * @version: 1.0.0
 */
public class YieldVsSleepSample {
    static void main() {
        // 线程1:用yield
        Thread yieldThread = new Thread(() -> {
            long start = System.currentTimeMillis();
            int count = 0;
            while (System.currentTimeMillis() - start < 1000) {
                count++;
                Thread.yield();
            }
            System.out.println("yield线程:1秒内循环" + count + "次");
        }, "Yield-Thread");

        // 线程2:用sleep(1ms)
        Thread sleepThread = new Thread(() -> {
            long start = System.currentTimeMillis();
            int count = 0;
            while (System.currentTimeMillis() - start < 1000) {
                count++;
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("sleep线程:1秒内循环" + count + "次");
        }, "Sleep-Thread");

        yieldThread.start();
        sleepThread.start();
    }
}

运行结果:调度差异明显
在这里插入图片描述

“看明白了吧?” 哇哥敲着桌子,“yield 是‘让而不退’,还在就绪队列里等着;sleep 是‘睡而不醒’,休眠时根本不在就绪队列 —— 就像工人,yield 是站在工位旁等,sleep 是回休息室躺着,能一样吗?”

四、面试必问:Thread.yield () 核心题(附答案)

哇哥知道王二要面试,特意整理了 3 道高频题,让他抄在小本本上,字里行间都是考点:

✔️ 面试题 1:Thread.yield () 的作用是什么?底层原理是什么?

用 “车间轮岗” 的例子答,面试官一听就懂:

  • 作用:线程主动放弃当前 CPU 时间片的剩余部分,从 “运行状态” 转为 “就绪状态”,提示操作系统重新调度 —— 就像工人举牌 “让工位”,但不离开车间;
  • 底层原理:调用 Unsafe 类的yield()本地方法(JDK 源码里是Unsafe.yield()),最终由操作系统的线程调度器决定后续调度:
    • 若有高优先级线程就绪,优先调度高优先级线程;
    • 若无高优先级线程,可能重新调度当前线程(同优先级线程公平竞争)。

🔥 面试题 2:Thread.yield () 和 Thread.sleep () 的核心区别是什么?

抓三个核心点,用 “工人工作” 比喻:

  • 线程状态:yield 是 “运行→就绪”,不阻塞,随时可能被重新调度;sleep 是 “运行→阻塞”,休眠期间不会被调度(除非被中断)—— 前者工人站着等,后者工人躺着睡;
  • 时间片控制:yield 只释放剩余时间片(比如还剩 5ms,直接让出去);sleep 释放全部时间片,休眠指定时间(比如睡 100ms,到点才醒);
  • 适用场景:yield 适合忙等优化(避免 CPU 空转);sleep 适合需要固定间隔的场景(比如每隔 1 秒轮询一次)。

➡️ 面试题 3:Thread.yield () 的使用场景有哪些?要注意什么?

适用场景:

  • 忙等优化:线程在循环中等待某个条件(如 volatile 变量变化),用 yield 避免 CPU 空转(比 sleep 响应更及时);
  • 平等调度:同优先级线程需要公平竞争 CPU 时,用 yield 避免单个线程长期占用;
  • 降低 CPU 占用:非核心线程在执行非紧急任务时,用 yield 减少对核心线程的干扰。

注意事项:

  • 不释放锁:yield 不会释放 synchronized 或 Lock 的锁,别用它解决锁竞争问题;
  • 调度不保证:yield 是 “提示” 不是 “命令”,操作系统可能忽略,不要依赖它实现线程顺序;
  • 低优先级慎用:低优先级线程调用 yield 后,可能长期得不到调度(被高优先级线程抢占)。

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

王二把核心知识点编成顺口溜,贴在键盘上,字丑但醒目:

  • yield 不释放锁,只让时间片就好;
  • 运行转就绪,不是阻塞别混淆;
  • 忙等循环加 yield,CPU 占用立马少;
  • sleep 睡够才醒,yield 随时可能跑;
  • 调度看 OS 脸色,别把它当救命草。

王二用优化后的忙等代码替换了之前的监控程序,CPU 占用率从 99% 降到了 5%,领导路过时瞥了眼监控,破天荒地说了句 “这代码写得像回事”。

王二拿着代码跑去找哇哥,脸上笑开了花。哇哥呷了口搪瓷缸里的茶,茶叶梗浮在水面,慢悠悠说出那句收尾的话:

哇哥说:“技术这东西,就像巷子里的老豆腐,看着不起眼的玩意儿,未必没真本事。你之前觉得 yield 是鸡肋,不过是没摸到它的脾气 —— 所谓精通,不是会用多少 API,是能把每个 API 的骨头都啃透,知道它该在哪站岗,不该在哪添乱。这世上本没有没用的技术,只有用错地方的人。”

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值