多線程 yield 方法
1. 前言
本節(jié)對 yield 方法進(jìn)行深入的剖析,主要內(nèi)容點如下:
- 首先要了解什么是 CPU 執(zhí)行權(quán),因為 yield 方法與 CPU 執(zhí)行權(quán)息息相關(guān);
- 了解 yield 方法的作用,要明確 yield 方法的使用所帶來的運(yùn)行效果;
- 了解什么是 native 方法,由于 yield 方法是 native 方法的調(diào)用,在學(xué)習(xí) yield 方法之前,要了解什么是 native 方法;
- 掌握 yield 方法如何使用,這是本節(jié)知識點的重中之重,一定要著重學(xué)習(xí);
- 了解 yield 方法和 sleep 方法的區(qū)別,進(jìn)行對比記憶,更有助于掌握該方法的獨有特性。
2. 什么是 CPU 執(zhí)行權(quán)
我們知道操作系統(tǒng)是為每個線程分配一個時間片來占有 CPU 的,正常情況下當(dāng)一個線程把分配給自己的時間片使用完后,線程調(diào)度器才會進(jìn)行下一輪的線程調(diào)度,這里所說的 “自己占有的時間片” 即 CPU 分配給線程的執(zhí)行權(quán)。
那進(jìn)一步進(jìn)行探究,何為讓出 CPU 執(zhí)行權(quán)呢?
當(dāng)一個線程通過某種可行的方式向操作系統(tǒng)提出讓出 CPU 執(zhí)行權(quán)時,就是在告訴線程調(diào)度器自己占有的時間片中還沒有使用完的部分自己不想使用了,主動放棄剩余的時間片,并在合適的情況下,重新獲取新的執(zhí)行時間片。
3. yield 方法的作用
方法介紹:Thread 類中有一個靜態(tài)的 yield 方法,當(dāng)一個線程調(diào)用 yield 方法時,實際就是在暗示線程調(diào)度器當(dāng)前線程請求讓出自己的 CPU 使用權(quán)。
public static native void yield();
Tips:從這個源碼中我們能夠看到如下兩點要點:
- yield 方法是一個靜態(tài)方法,靜態(tài)方法的特點是可以由類直接進(jìn)行調(diào)用,而不需要進(jìn)行對象 new 的創(chuàng)建,調(diào)用方式為 Thread.yield ()。
- 該方法除了被 static 修飾,還被 native 修飾,那么進(jìn)入主題,什么是 native 方法呢?我們繼續(xù)來看下文的講解。
抽象地講,一個 Native Method 就是一個 Java 調(diào)用的非 Java 代碼的接口。一個 Native Method 是這樣一個 Java 的方法:該方法的實現(xiàn)由非 java 語言實現(xiàn)。
簡單的來說,native 方法就是我們自己電腦的方法接口,比如 Windows 電腦會提供一個 yield 方法,Linux 系統(tǒng)的電腦也同樣會提供一個 yield 方法,本地方法,可以理解為操作調(diào)用操作系統(tǒng)的方法接口。
作用:暫停當(dāng)前正在執(zhí)行的線程對象(及放棄當(dāng)前擁有的 cup 資源),并執(zhí)行其他線程。yield () 做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級的其他線程獲得運(yùn)行機(jī)會。
目的:yield 即 “謙讓”,使用 yield () 的目的是讓具有相同優(yōu)先級的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實際中無法保證 yield () 達(dá)到謙讓目的,因為放棄 CPU 執(zhí)行權(quán)的線程還有可能被線程調(diào)度程序再次選中。
4. yield 方法如何使用
為了更好的了解 yield 方法的使用,我們首先來設(shè)計一個使用的場景。
場景設(shè)計:
- 創(chuàng)建一個線程,線程名為 threadOne;
- 打印一個數(shù),該數(shù)的值為從 1 加到 10000000 的和;
- 不使用 yield 方法正常執(zhí)行,記錄總的執(zhí)行時間;
- 加入 yield 方法,再次執(zhí)行程序;
- 再次記錄總執(zhí)行時間。
期望結(jié)果: 未加入 yield 方法之前打印的時間 < 加入 yield 方法之后的打印時間。因為 yield 方法在執(zhí)行過程中會放棄 CPU 執(zhí)行權(quán)并從新獲取新的 CPU 執(zhí)行權(quán)。
代碼實現(xiàn) - 正常執(zhí)行:
public class DemoTest extends Thread {
@Override
public void run() {
Long start = System.currentTimeMillis();
int count = 0;
for (int i = 1; i <= 10000000; i++) {
count = count + i;
}
Long end = System.currentTimeMillis();
System.out.println("總執(zhí)行時間: "+ (end-start) + " 毫秒, 結(jié)果 count = " + count);
}
public static void main(String[] args) throws InterruptedException {
DemoTest threadOne = new DemoTest();
threadOne. start();
}
}
執(zhí)行結(jié)果驗證:
總執(zhí)行時間: 6 毫秒.
代碼實現(xiàn) - yield 執(zhí)行:
public class DemoTest extends Thread {
@Override
public void run() {
Long start = System.currentTimeMillis();
int count = 0;
for (int i = 1; i <= 10000000; i++) {
count = count + i;
this.yield(); // 加入 yield 方法
}
Long end = System.currentTimeMillis();
System.out.println("總執(zhí)行時間: "+ (end-start) + " 毫秒. ");
}
public static void main(String[] args) throws InterruptedException {
DemoTest threadOne = new DemoTest();
threadOne. start();
}
}
執(zhí)行結(jié)果驗證:
總執(zhí)行時間: 5377 毫秒.
從執(zhí)行的結(jié)果來看,與我們對 yield 方法的理解和分析完全相符,請同學(xué)也進(jìn)行代碼的編寫和運(yùn)行,加深學(xué)習(xí)印象。當(dāng)加入 yield 方法執(zhí)行時,線程會放棄 CPU 的執(zhí)行權(quán),并等待再次獲取新的執(zhí)行權(quán),所以執(zhí)行時間上會更加的長。
5. yield 方法和 sleep 方法的區(qū)別
- sleep () 方法給其他線程運(yùn)行機(jī)會時不考慮線程的優(yōu)先級,因此會給低優(yōu)先級的線程以運(yùn)行的機(jī)會;
- yield () 方法只會給相同優(yōu)先級或更高優(yōu)先級的線程以運(yùn)行的機(jī)會;
- 線程執(zhí)行 sleep () 方法后轉(zhuǎn)入阻塞 (blocked) 狀態(tài),而執(zhí)行 yield () 方法后轉(zhuǎn)入就緒 (ready) 狀態(tài);
- sleep () 方法聲明會拋出 InterruptedException, 而 yield () 方法沒有聲明任何異常;
- sleep () 方法比 yield () 方法具有更好的移植性 (跟操作系統(tǒng) CPU 調(diào)度相關(guān))。
6. 小結(jié)
在實際的開發(fā)場景中,yield 方法的使用場景比較少,但是對于并發(fā)原理知識的學(xué)習(xí)過程,對 yield 方法的了解非常重要,有助于同學(xué)了解不同狀態(tài)下的線程的不同狀態(tài)。
本節(jié)要重點掌握 yield 方法的作用以及如何使用 yield 方法。