簡介
什么是線程
進程:一個進程包括由操作系統(tǒng)分配的內(nèi)存空間,包含一個或多個線程奈懒。一個進程一直運行遭商,直到所有的非守護線程都結(jié)束運行后才能結(jié)束。
線程:一個線程不能獨立的存在缅帘,它必須是進程的一部分轴术。
線程的生命周期
實現(xiàn)方式
大家可能在面試過程中、文章中或者書上看到過“實現(xiàn)線程有幾種方式钦无?”這樣的問題逗栽,基本都是千篇一律的說兩種方式:1、繼承Thread重寫run方法失暂;2彼宠、實現(xiàn)Runnable接口鳄虱。其實這里是有問題的,總所周知在Java中使用Thread表示線程的兵志,所以實現(xiàn)Thread只有一種方式:那就是構(gòu)造Thread類醇蝴。而實現(xiàn)線程的執(zhí)行單元有兩種方式:1、繼承Thread重寫run方法想罕;2悠栓、實現(xiàn)Runnable接口的run方法,并將Runnable實例作為構(gòu)造Thread的參數(shù)按价。
API講解
構(gòu)造函數(shù)
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
...
}
強烈建議:在構(gòu)造線程的時候為線程取一個有特殊意義的名字惭适,有助于問題的排查和線程的跟蹤(特別是在線程比較多的程序中)。
Thread.sleep和TimeUnit
sleep()方法導(dǎo)致程序暫停執(zhí)行指定的時間楼镐,讓出cpu給其他線程癞志,但是他的監(jiān)控狀態(tài)依然保持者,當(dāng)指定的時間到了又會自動恢復(fù)運行狀態(tài)框产。在調(diào)用sleep()方法的過程中凄杯,線程不會釋放對象鎖。
sleep是一個靜態(tài)方法秉宿,有兩個重載方法戒突,其中一個需要傳入毫秒數(shù),另一個既需要毫秒數(shù)也需要納秒數(shù)描睦。
public static void sleep(long millis)
public static void sleep(long millis, int nanos)
TimeUnit是JDK1.5以后引入的膊存,其對sleep有更好的封裝性,能更好地忱叭、更精準(zhǔn)地控制隔崎。
TimeUnit.HOURS.sleep(1);
TimeUnit.MUNUTES.sleep(1);
TimeUnit.SECONDS.sleep(1);
強烈建議:用TimeUnit代替Thread.sleep,因為sleep能做的事韵丑,TimeUnit都能做并且功能更強大爵卒。
Thread.yield
yield方法屬于一種啟發(fā)式的方法,其會提醒調(diào)度器我愿意放棄當(dāng)前CPU資源撵彻。如果CPU資源不緊張钓株,則會忽略這種提醒。如果生效千康,會使當(dāng)前線程從Running狀態(tài)切換到Runnable狀態(tài)享幽。
new Thread(() -> {
Thread.yield();
System.out.println("yield");
}
).start()
yield和sleep區(qū)別
- sleep會導(dǎo)致線程暫停指定時間铲掐,會使線程處于block狀態(tài)拾弃;yield如果CPU調(diào)度器沒有忽略,會導(dǎo)致線程處于Runnable狀態(tài)摆霉。
- sleep幾乎百分之百地完成給定時間的休眠豪椿;yield并不能保證成功奔坟。
- 一個線程sleep,另一個線程調(diào)用interrupt會捕獲到中斷信號搭盾;yield不會咳秉。
join
join()方法是Thread類中的一個方法,該方法的定義是等待該線程終止鸯隅。其實就是join()方法將掛起調(diào)用線程的執(zhí)行澜建,直到被調(diào)用的對象完成它的執(zhí)行。join與sleep一樣是一個可中斷的方法蝌以。
ThreadTest test =new ThreadTest();
ThreadTest test2 =new ThreadTest();
test.setName("one");
test2.setName("two");
Thread t1 = new Thread(test);
Thread t2 = new Thread(test2);
t1.start();
/**
* 主線程向下轉(zhuǎn)時炕舵,碰到了t1.join(),t1要申請加入到運行中來,就是要CPU執(zhí)行權(quán)跟畅。
* 這時候CPU執(zhí)行權(quán)在主線程手里咽筋,主線程就把CPU執(zhí)行權(quán)給放開,陷入凍結(jié)狀態(tài)徊件。
* 活著的只有t1了奸攻,只有當(dāng)t1拿著執(zhí)行權(quán)把這些數(shù)據(jù)都打印完了,主線程才恢復(fù)到運行中來
*/
//join 方法 確保 t1執(zhí)行之后 執(zhí)行t2
t1.join();
t2.start();
join和CountDownLatch區(qū)別
- 都適用“主程序中需要等待所有子線程完成后 再繼續(xù)任務(wù)”的場景
- 調(diào)用join方法需要等待thread執(zhí)行完畢才能繼續(xù)向下執(zhí)行,而CountDownLatch只需要檢查計數(shù)器的值為零就可以繼續(xù)向下執(zhí)行虱痕,相比之下睹耐,CountDownLatch更加靈活一些,可以實現(xiàn)一些更加復(fù)雜的業(yè)務(wù)場景皆疹。
interrupt疏橄、interrupted、isInterrupted
interrupt
interrupt()方法: 作用是中斷線程略就。
- 本線程中斷自身是被允許的捎迫,且"中斷標(biāo)記"設(shè)置為true
- 其它線程調(diào)用本線程的interrupt()方法時,會通過checkAccess()檢查權(quán)限表牢。這有可能拋出SecurityException異常窄绒。
- 若線程在阻塞狀態(tài)時,調(diào)用了它的interrupt()方法崔兴,那么它的“中斷狀態(tài)”會被清除并且會收到一個InterruptedException異常彰导。
例如:線程通過wait()、sleep()敲茄、join()進入阻塞狀態(tài)位谋,此時通過interrupt()中斷該線程;調(diào)用interrupt()會立即將線程的中斷標(biāo)記設(shè)為“true”堰燎,但是由于線程處于阻塞狀態(tài)掏父,所以該“中斷標(biāo)記”會立即被清除為“false”,同時秆剪,會產(chǎn)生一個InterruptedException的異常赊淑。
如果線程被阻塞在一個Selector選擇器中爵政,那么通過interrupt()中斷它時;線程的中斷標(biāo)記會被設(shè)置為true陶缺,并且它會立即從選擇操作中返回钾挟。 - 如果不屬于前面所說的情況,那么通過interrupt()中斷線程時饱岸,它的中斷標(biāo)記會被設(shè)置為“true”掺出。
interrupted和isInterrupted
- interrupted和isInterrupted都是判斷當(dāng)前線程是否被中斷
- interrupted會清除掉線程的interrupt標(biāo)志;isInterrupted不會
System.out.println("Main Thread is interrupted? " + Thread.interrupted());
Thread.currentThread().interrupt();
System.out.println("Main Thread is interrupted? " + Thread.currentThread().isInterrupted());
// System.out.println("Main Thread is interrupted? " + Thread.interrupted());
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
System.out.println("I will be interrupted still.");
}
思考:打開注釋和關(guān)閉注釋的區(qū)別苫费?
如何關(guān)閉一個線程
- 正常關(guān)閉
1蛛砰、 線程正常結(jié)束,線程自動退出
2黍衙、捕獲中斷信號
3泥畅、適用volatile開關(guān)控制 - 異常退出
補充:Thread API中包含了一個stop()方法,可以突然終止線程琅翻。但它在JDK1.2后便被淘汰了位仁,因為它可能導(dǎo)致數(shù)據(jù)對象的崩潰。一個問題是方椎,當(dāng)線程終止時聂抢,很少有機會執(zhí)行清理工作;另一個問題是棠众,當(dāng)在某個線程上調(diào)用stop()方法時琳疏,線程釋放它當(dāng)前持有的所有鎖,持有這些鎖必定有某種合適的理由——也許是阻止其他線程訪問尚未處于一致性狀態(tài)的數(shù)據(jù)闸拿,突然釋放鎖可能使某些對象中的數(shù)據(jù)處于不一致狀態(tài)空盼,而且不會出現(xiàn)數(shù)據(jù)可能崩潰的任何警告。
守護線程
若JVM中沒有一個非守護線程時新荤,JVM的進行會退出揽趾。
守護線程具備自動結(jié)束生命周期的特性,經(jīng)常用于執(zhí)行一些后臺工作苛骨,因此有時它也被稱為后臺線程篱瞎。
//偽代碼如下
Thread thread = new Thread();
thread.setDaemon(true);
thread.start()