sleep
sleep是一個靜態(tài)方法,只有兩個重載方法,其中一個傳入毫秒數(shù), 另一個既需要毫秒數(shù)也需要納秒數(shù)。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException
sleep方法會使當(dāng)前線程進(jìn)入指定毫秒的休眠,暫停執(zhí)行,雖然給定了休眠的時間,但是最終要以系統(tǒng)的定時器和調(diào)度器的精度為準(zhǔn),
sleep的休眠有一個很重要的特性,就是不會放棄monitor鎖的所有權(quán)
JDK 1.5以后,引入了一個枚舉TimeUnit 其對sleep方法進(jìn)行了很好的封裝
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
使用實(shí)例
TimeUnit.SECONDS.sleep(10);
TimeUnit.MILLISECONDS.sleep(200);
TimeUnit.HOURS.sleep(1);
yield
yield方法屬于一種啟發(fā)式的方法,其會提醒調(diào)度器我愿意放棄當(dāng)前的CPU資源,如果CPU資源不緊張,則會忽略這個提醒。即yield只是一個提示,CPU調(diào)度器并不會擔(dān)保每次都能滿足yield的提示。
yield VS sleep
- 是否導(dǎo)致線程上下文的切換
sleep會導(dǎo)致當(dāng)前線程暫停指定的時間,沒有CPU的時間片消耗
yield只會CPU調(diào)度器發(fā)一個提示,如果CPU調(diào)度器不緊張則會忽略這個提示,如果沒有忽略這個提示,會導(dǎo)致線程上下文的切換
導(dǎo)致的線程狀態(tài)切換
sleep 會導(dǎo)致線程短暫進(jìn)入Block, 會在給定的時間內(nèi)釋放CPU資源
yield 會是Running狀態(tài)的線程進(jìn)入Runnable的狀態(tài)(如果CPU調(diào)度器沒有忽略這個提示的話)時間上
sleep 幾乎百分百完成給定時間的休眠
yield 的提示并不能一定擔(dān)保中斷信號
一個線程sleep 另一個線程調(diào)用interrupt 會捕獲到中斷信號
yield則不會
priority
線程的優(yōu)先級,理論上優(yōu)先級比較高的線程會獲得優(yōu)先被CPU調(diào)度的機(jī)會。但是事實(shí)上并不會如你所愿,設(shè)置線程的優(yōu)先級也是一個hint知操作:
- 對于root用戶,它會hint操作系統(tǒng)你想要的設(shè)置的優(yōu)先級別,否則它會被忽略
- 如果CPU太忙,設(shè)置優(yōu)先級可能會獲得更多CPU時間片,但是閑時優(yōu)先級的高低幾乎不會有任何作用。
所以,不要在程序設(shè)計(jì)中企圖用線程的優(yōu)先級來綁定某些特性業(yè)務(wù),或者讓業(yè)務(wù)嚴(yán)重依賴線程的優(yōu)先級。
線程的默認(rèn)優(yōu)先級
/**
* The minimum priority that a thread can have.
*/
public static final int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public static final int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public static final int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
上面源碼分析得出,線程的優(yōu)先級不能大于10 和小于1 , 如果指定線程的優(yōu)先級大于線程所在組的最大優(yōu)先級,則指定優(yōu)先級將會失效,取而代之的是當(dāng)前線程所在組的最大線程優(yōu)先級。如果小于所在組的最大線程優(yōu)先級, 則設(shè)置為需要設(shè)置的線程優(yōu)先級。
線程的上下文類加載器
線程的上下文類加載器,就是這個線程是有那個類加載器加載的,如果在沒有修改線程上下文類加載器的情況下,則保持與父線程同樣的類加載器
如果設(shè)置該線程的類加載器,這個方法則可以打破Java類加載器的父委托機(jī)制,有時也稱為Java類加載器的后門。
interrupt
public void interrupt();
/**
此方法是一個靜態(tài)方法,雖然也用于對當(dāng)前線程是否被中斷,但是和成員方法isInterrupted方法的區(qū)別,
就是調(diào)用該方法會直接擦除掉線程的interrupt標(biāo)識,
注意:如果當(dāng)前線程被打斷了,那么第一次調(diào)用interrupted方法會返回true,
并且立即擦除interrupt標(biāo)識, 第二次包括以后調(diào)用永遠(yuǎn)都會返回false,
除非再次期間線程又一次被打斷
*/
public static boolean interrupted();
/**
Thread的成員方法,主要是判斷當(dāng)前線程是否被中斷,該方法
僅僅是對interrupt標(biāo)識的一個判斷,并不會影響標(biāo)識發(fā)生任何改變
*/
public boolean isInterrupted();
調(diào)用如下方法會使當(dāng)前線程進(jìn)入阻塞狀態(tài)
- Object 的wait()
- Object 的wait(long)
- Object 的wait(long, int)
- Thread 的sleep(long)
- Thread 的sleep(long, int)
- Thread 的join()
- Thread 的join(long)
- Thread 的join(long, int)
- InterruptibleChannel的IO操作
- Selector的wakeup方法
- 其他
而調(diào)用當(dāng)前線程的Interrupt方法,則就可以打斷阻塞, 因此此方法也稱為可中斷方法。
注意:
打斷一個線程不等于該線程的生命周期結(jié)束,僅僅是打斷了當(dāng)前線程的阻塞狀態(tài)。
如果一個線程執(zhí)行可中斷方法被阻塞時,調(diào)用interrupt方法將其中斷,會導(dǎo)致其中斷標(biāo)識被清除。
如果一個線程已經(jīng)試死亡狀態(tài),那么嘗試對其調(diào)用interrupt方法會直接被忽略
join
與sleep一樣, 它也是一個可中斷方法。也即如果有其他線程執(zhí)行了對當(dāng)前線程的interrupt操作,它也會捕捉到中斷信號,并且擦除線程的interrupt標(biāo)識。
join某個線程A , 會使當(dāng)前線程B(比如main線程)進(jìn)入等待,知道線程A結(jié)束生命周期,或者到達(dá)指定的時間,那么在此期間線程B是Blocked的
實(shí)例:
比如需要你做一個接口請求,這個請求的返回需要調(diào)用多個接口的返回進(jìn)行組合,這里用join來做并行多個請求,等都返回后再進(jìn)行組合,當(dāng)然也可用JDK自帶的CountDownLatch 或者CyclicBarrier等,這里我們用join來實(shí)現(xiàn)一把。
public class ClientRequestTask extends Thread {
public static void main(String[] args) {
List<ClientRequestTask> tasks = IntStream.range(1, 3).boxed()
.map(url -> new ClientRequestTask("url:" + url)).collect(Collectors.toList());
tasks.forEach(Thread::start);
tasks.forEach(task -> {
try {
task.join();
} catch (InterruptedException e) {
}
});
List<String> list = new ArrayList<>();
tasks.stream().map(ClientRequestTask::getResults).forEach(list::addAll);
System.out.println(list);
}
private String url;
private final List<String> results = new ArrayList<>();
public ClientRequestTask(String url) {
this.url = url;
}
public List<String> getResults() {
return results;
}
@Override
public void run() {
try {
System.out.println(url + " start request data");
int i = ThreadLocalRandom.current().nextInt(10);
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
results.add(url + "---------" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如何關(guān)閉一個線程
- JDK有一個Deprecated的方法stop, 但是該方法關(guān)閉線程時不會釋放掉monitor鎖,所以官方已經(jīng)不推薦使用此方法了。
- 線程結(jié)束生命周期
- 捕獲中斷信號關(guān)閉線程
- 使用volatile開關(guān)控制
- 異常退出