Thread.yield() 是一個提示給執行緒排程器的建議:「我願意讓出 CPU 給同優先級的其他執行緒」。但它不保證會發生上下文切換,也不釋放鎖。本文深度解析 yield() 行為、與 wait()/sleep() 差異、實測驗證與適用場景。
class YieldTask extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.printf("%s [優先級:%d]: %d%n", getName(), getPriority(), i);
if (i % 4 == 0) {
Thread.yield(); // 提示讓步
}
}
}
}
public class YieldDemo {
public static void main(String[] args) {
new YieldTask().start(); // t1
new YieldTask().start(); // t2
}
}
可能輸出 (單核心環境較明顯):
t1 [5]: 0
t1 [5]: 1
t1 [5]: 2
t1 [5]: 3
t1 [5]: 4 <- yield() 但可能繼續執行
t1 [5]: 5
...
t2 [5]: 0 <- t2 可能很久後才獲得 CPU
public class YieldLockDemo {
private static final Object LOCK = new Object();
static class LockedTask extends Thread {
@Override
public void run() {
synchronized (LOCK) { // 持有鎖
for (int i = 0; i < 10; i++) {
System.out.printf("%s: %d%n", getName(), i);
if (i % 4 == 0) Thread.yield(); // 即使 yield,鎖仍持有
}
}
}
}
public static void main(String[] args) {
new LockedTask().start(); // t1
new LockedTask().start(); // t2 - 將被阻塞直到 t1 釋放鎖
}
}
輸出:t1 完整執行 0-9,t2 才開始執行。證明 yield() 期間鎖未釋放。
| 方法 | 狀態轉換 | 釋放鎖 | 喚醒條件 | 典型用途 |
|---|---|---|---|---|
yield() |
RUNNING → RUNNABLE | 否 | 排程器決定 | 禮讓同優先級執行緒、長時間計算中插入讓步點 |
sleep(ms) |
RUNNING → TIMED_WAITING | 否 | 時間到期 | 定時輪詢、節流、模擬延遲 |
wait() |
RUNNING → WAITING | 是 (釋放該物件監視器鎖) | notify()/notifyAll() |
執行緒間協調、生產者消費者 |
park() |
RUNNING → WAITING | 否 | unpark() |
LockSupport、CompletableFuture 底層 |
public class SleepLockDemo {
private static final Object LOCK = new Object();
static class SleepTask extends Thread {
@Override
public void run() {
synchronized (LOCK) {
for (int i = 0; i < 10; i++) {
System.out.printf("%s: %d%n", getName(), i);
if (i % 4 == 0) {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
}
}
}
}
}
public static void main(String[] args) {
new SleepTask().start(); // t1
new SleepTask().start(); // t2 - 同樣被阻塞
}
}
結論:sleep() 同樣不釋放鎖。只有 wait()、Condition.await() 會釋放鎖。
void heavyComputation() {
for (int i = 0; i < 1_000_000; i++) {
// 複雜計算...
if (i % 10000 == 0) {
Thread.yield(); // 讓其他同優先級任務有機會執行
}
}
}
class SimpleSpinLock {
private final AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
while (!locked.compareAndSet(false, true)) {
Thread.yield(); // 自旋時讓步,避免佔滿 CPU
}
}
public void unlock() {
locked.set(false);
}
}
@Test
void testConcurrentAccess() {
// 在關鍵區插入 yield 增加執行緒交錯,暴露競態條件
Thread.yield();
}
Lock、Condition、Semaphore、CountDownLatch、CompletableFuture 等高層同步工具-XX:+UseThreadPriorities、-XX:ThreadPriorityPolicy 影響排程行為| 誤區 | 事實 |
|---|---|
| yield() 會讓出 CPU 給低優先級執行緒 | 僅讓給相同優先級;低優先級需靠排程器自然輪換 |
| yield() 保證上下文切換 | 無保證,視 JVM 實作、OS 排程器、核心數而定 |
| yield() 可替代 wait()/sleep() | 語意完全不同:yield 不釋放鎖、無時間控制、無通知機制 |
| 生產代碼大量使用 yield | 通常是設計缺陷;高層同步工具更安全、可維護 |
Thread.yield() 是建議性、非強制性的讓步提示,不釋放鎖、不保證切換、僅針對同優先級。理解其與 sleep() (定時休眠不釋放鎖)、wait() (釋放鎖等待通知) 的本質差異,是編寫正確併發程式的基礎。現代開發應優先使用高層同步工具,僅在自旋鎖、長計算讓步點、測試交錯等特定場景謹慎使用 yield()。