在上一篇中介紹了 Thread 類的構(gòu)造方法烹困,可是光有構(gòu)造方法也不夠晶渠,我們還得再學(xué)習(xí)多一些該類常用的 API 才行,這樣才能對(duì)該類有更深刻的了解曲伊,同時(shí)也能讓我們有更多的選擇叽讳。
Thread類提供的API有幾十個(gè),由于篇幅問題坟募,本篇文章僅選擇幾個(gè)有代表性的來進(jìn)行講解岛蚤。剩余的API小伙伴們感興趣的可以通過源碼進(jìn)行查看,也可以給我留言懈糯,我們共同探討共同學(xué)習(xí)涤妒。
目標(biāo)
- currentThread
- setPriority
- yield
- sleep
- interrupt
- interrupted
- join
內(nèi)容
1. currentThread
該方法用于返回當(dāng)前執(zhí)行線程的引用,我們可以在代碼塊中通過它來獲取當(dāng)前的線程對(duì)象赚哗,雖然看起來很簡(jiǎn)單她紫,但是使用非常廣泛,在后續(xù)的內(nèi)容中都會(huì)大量使用到該方法蜂奸。
方法:
public static native Thread currentThread();
代碼:
/**
* 這個(gè)例子我們可以看到 Thread.currentThread() 這里拿到的都是各自的執(zhí)行線程引用對(duì)象犁苏。
*/
public class CurrentThreadDemo {
public static void main(String[] args) {
// 打印結(jié)果為:true
Thread t = new Thread(() -> {
System.out.println("t".equals(Thread.currentThread().getName()));
}, "t");
t.start();
// 打印結(jié)果為:true
System.out.println("main".equals(Thread.currentThread().getName()));
}
}
2. setPriority
進(jìn)程有進(jìn)程的優(yōu)先級(jí),線程同樣也有優(yōu)先級(jí)扩所,理論上是優(yōu)先級(jí)比較高的線程會(huì)優(yōu)先被CPU進(jìn)行調(diào)度,但事實(shí)上往往并不會(huì)如你所愿朴乖。
如果CPU比較忙祖屏,設(shè)置優(yōu)先級(jí)可能會(huì)獲得更多的CPU時(shí)間片,但是在CPU空閑的情況下买羞,設(shè)置優(yōu)先級(jí)幾乎不會(huì)有任何作用袁勺。所以,我們不要試圖在程序設(shè)計(jì)中使用優(yōu)先級(jí)來綁定某些業(yè)務(wù)或者讓業(yè)務(wù)依賴于優(yōu)先級(jí)畜普,這產(chǎn)生的結(jié)果可能會(huì)與你所期望的結(jié)果不一致期丰。
方法:
public final void setPriority(int newPriority); // 設(shè)置線程優(yōu)先級(jí)
public final int getPriority(); // 獲取線程優(yōu)先級(jí)
案例:
/**
* t1 線程的優(yōu)先級(jí)比 t2 線程的低,正常來說應(yīng)該統(tǒng)計(jì)數(shù)量會(huì)比 t2 線程的少
* 但是我這里隨機(jī)測(cè)試統(tǒng)計(jì)到的次數(shù)為:
* t1: 59573
* t2: 34321
* 不同的CPU資源情況會(huì)有不同的運(yùn)行結(jié)果,小伙伴們可以多測(cè)試幾次看看
*/
public class PriorityDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println("t1");
}
}, "t1");
Thread t2 = new Thread(() -> {
while (true) {
System.out.println("t2");
}
}, "t2");
// 最小值為:1钝荡,中間值為:5街立,最大值為:10
t1.setPriority(9);
t2.setPriority(10);
t1.start();
t2.start();
}
}
3. yield
yield方法屬于一種啟發(fā)式的方法,它給調(diào)度程序的提示是當(dāng)前線程愿意放棄對(duì)處理器的當(dāng)前使用埠通。調(diào)度程序可以隨意忽略此提示赎离。應(yīng)將其使用與詳細(xì)的性能分析和基準(zhǔn)測(cè)試結(jié)合起來,以確保它實(shí)際上具有所需的效果端辱。
此方法很少被使用梁剔。對(duì)于調(diào)試或測(cè)試目的,它可能很有用舞蔽,因?yàn)樗赡苡兄谥噩F(xiàn)由于競(jìng)爭(zhēng)條件而產(chǎn)生的錯(cuò)誤荣病。
方法:
public static native void yield();
案例:
/**
* 將注釋部分放開的話可以看到控制臺(tái)輸出的結(jié)果有時(shí)候是 0 在前面,有時(shí)候是 1 在前面
* 而將該部分注釋之后渗柿,你會(huì)發(fā)現(xiàn)一只都是 0 在前面
*/
public class YieldDemo {
public static void main(String[] args) {
IntStream.range(0, 2).mapToObj(YieldDemo::test).forEach(Thread::start);
}
private static Thread test(int index){
return new Thread(() -> {
// if (index == 0){
// Thread.yield();
// }
System.out.println(index);
});
}
}
4. sleep
sleep是一個(gè)靜態(tài)方法众雷,根據(jù)系統(tǒng)計(jì)時(shí)器和調(diào)度程序的精度和準(zhǔn)確性,使當(dāng)前正在執(zhí)行的線程進(jìn)入休眠狀態(tài)(暫時(shí)停止執(zhí)行)達(dá)指定的毫秒數(shù)做祝。該線程不會(huì)失去任何監(jiān)視器的所有權(quán)(例如monitor鎖砾省,關(guān)于monitor鎖在后續(xù)的文章中會(huì)進(jìn)行詳細(xì)講解)。
方法:
public static native void sleep(long millis); // 休眠的毫秒數(shù)
public static void sleep(long millis, int nanos); // 休眠的毫秒數(shù)與納秒數(shù)
案例:
/**
* 在這個(gè)例子中混槐,我們分別在自定義線程喝主線程中進(jìn)行了休眠编兄,每個(gè)線程的休眠互不影響
* Thread.sleep() 只會(huì)導(dǎo)致當(dāng)前線程休眠指定的時(shí)間
*/
public class SleepDemo {
public static void main(String[] args) {
new Thread(() -> {
long startTime = System.currentTimeMillis();
sleep(2000);
System.out.printf("%s線程耗時(shí):%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
System.out.println("");
}, "t").start();
long startTime = System.currentTimeMillis();
sleep(3000);
System.out.printf("%s線程耗時(shí):%d%s", Thread.currentThread().getName(), System.currentTimeMillis() - startTime, "ms");
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
yield 和 sleep 的區(qū)別:
- yield 只是對(duì) CPU 調(diào)度器的一個(gè)提示,如果生效了声登,會(huì)導(dǎo)致線程上下文的切換狠鸳;
- sleep 會(huì)導(dǎo)致當(dāng)前線程暫停指定的時(shí)間,沒有 CPU 時(shí)間片的消耗悯嗓;
- yield 會(huì)使線程由 RUNNING 狀態(tài)進(jìn)入 RUNNABLE 狀態(tài)件舵;
- sleep 會(huì)使線程短暫 block,之后在給定的時(shí)間內(nèi)釋放 CPU 資源脯厨;
- yield 不能保證一定生效铅祸,而 sleep 幾乎百分百的按照指定時(shí)間進(jìn)行休眠(最終休眠時(shí)間要以系統(tǒng)的定時(shí)器和調(diào)度器的精度為準(zhǔn))
- yield 無法捕獲中斷信號(hào),而 sleep 被另一個(gè)線程打斷則能捕獲到中斷信號(hào)
5. interrupt
線程interrupt是一個(gè)非常重要的API合武,也是經(jīng)常使用的方法临梗,如果在調(diào)用:
Object類的 wait(),wait(long)或wait(long稼跳,int)方法或join()盟庞,join(long),join(long汤善,int)的方法時(shí)阻塞了此線程什猖,此類的sleep(long)或sleep(long票彪,int)方法,則其中斷狀態(tài)將被清除不狮,并將收到
InterruptedException
降铸。InterruptibleChannel 的 I/O 操作,則該通道將被關(guān)閉荤傲,該線程的中斷狀態(tài)將被設(shè)置垮耳,并且該線程將收到
java.nio.channels.ClosedByInterruptException
。Selector 的 wakeup 方法遂黍,則將設(shè)置該線程的中斷狀態(tài)终佛,并且它將立即從選擇操作中返回(可能具有非零值),就像調(diào)用選擇器的喚醒方法一樣雾家。
與之相關(guān)的API還有幾個(gè)铃彰。
方法:
public void interrupt(); // 中斷阻塞
public static boolean interrupted(); // 判斷當(dāng)前線程是否被中斷,該方法會(huì)直接擦除掉線程的標(biāo)識(shí)
public boolean isInterrupted(); // 判斷當(dāng)前線程是否被中斷芯咧,僅做判斷不影響標(biāo)識(shí)
// interrupted 和 isInterrupted 方法都是調(diào)用本地方法 isInterrupted() 來實(shí)現(xiàn)牙捉,該方法中的參數(shù) ClearInterrupted 主要用來控制是否擦除線程 interrupt 的標(biāo)識(shí),該標(biāo)識(shí)被擦除后敬飒,后續(xù)的所有判斷都將會(huì)是 false
private native boolean isInterrupted(boolean ClearInterrupted);
案例:
/**
* 新建一個(gè)線程 t邪铲,休眠 1分鐘
* 主線程休眠 2秒鐘之后,對(duì)線程 t進(jìn)行打斷无拗,控制臺(tái)輸出:interrupt带到,程序結(jié)束
*/
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(60 * 1000);
} catch (InterruptedException e) {
System.out.println("interrupt");
}
}, "t");
t.start();
Thread.sleep(2000);
t.interrupt();
}
}
在線程內(nèi)部中存在一個(gè)名為 interrupt flag
的標(biāo)識(shí),如果一個(gè)線程被 interrupt
英染,那么它的flag
將被設(shè)置揽惹,在源碼中我們也可以看到有對(duì)應(yīng)的描述。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
但是如果當(dāng)前線程正在執(zhí)行可中斷方法被阻塞時(shí)四康,調(diào)用interrupt
方法將其中斷搪搏,反而會(huì)導(dǎo)致flag
被清楚,關(guān)于這點(diǎn)在后面的文章中還會(huì)詳細(xì)介紹闪金。
7. join
Thread 的 join 方法同樣也是一個(gè)非常重要的方法疯溺,使用它的特性可以實(shí)現(xiàn)很多比較強(qiáng)的功能,在 Thread 類中給我們提供了三個(gè)不同的 方法毕泌,具體如下喝检。
方法:
public final void join(); // 永久等待該線程生命周期結(jié)束
public final synchronized void join(long millis); // 設(shè)置最長(zhǎng)等待毫秒值,為 0 則永久等待
public final synchronized void join(long millis, int nanos); // 設(shè)置最長(zhǎng)等待毫秒值與納秒值撼泛,為 0 則永久等待
案例:
/**
* 創(chuàng)建兩個(gè)線程 1 和 2 并分別啟動(dòng),在控制臺(tái)進(jìn)行輸出澡谭。
* 同時(shí)main線程調(diào)用這兩個(gè)線程的方法愿题,這時(shí)你會(huì)發(fā)現(xiàn)線程 1 和 2 會(huì)交替的輸出直到兩個(gè)線程都運(yùn)行完畢
* 此時(shí)main線程才開始循環(huán)輸出损俭,如果將 join 方法注釋掉,則三個(gè)線程會(huì)同時(shí)進(jìn)行輸出
*/
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
List<Thread> list = IntStream.range(1, 3).mapToObj(JoinDemo::getThread).collect(Collectors.toList());
list.forEach(Thread::start);
for (Thread thread : list) {
thread.join();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static Thread getThread(int name){
return new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, String.valueOf(name));
}
}
總結(jié)
在本篇文章中潘酗,我們學(xué)習(xí)了 Thread 的一些較常見的 API杆兵,Thread 的 API 是掌握高并發(fā)編程的基礎(chǔ),非常有必要熟練掌握仔夺!
今天的文章到這里就結(jié)束了琐脏,小伙伴們有什么建議或者意見請(qǐng)聯(lián)系我改進(jìn)哦,你們的支持是我最大的動(dòng)力8淄谩H杖埂!
本文由博客群發(fā)一文多發(fā)等運(yùn)營(yíng)工具平臺(tái) OpenWrite 發(fā)布