一脂凶、Java 中的線程狀態(tài)轉(zhuǎn)換
【注】不是 start 之后就立刻開始執(zhí)行吸耿,只是就緒了(CPU 可能正在運(yùn)行其他的線程)。只有被 CPU 調(diào)度之后棒动,線程才開始執(zhí)行婚脱,當(dāng) CPU 分配給的時(shí)間片到了雹仿,又回到就緒狀態(tài)嘱吗,繼續(xù)排隊(duì)等候玄组。
二、線程控制的基本方法
- isAlive():判斷線程是否還活著柜与。start 之后巧勤,終止之前都是活的嵌灰。
- getPriority():獲得線程的優(yōu)先級(jí)數(shù)值弄匕。
- setPriority():設(shè)置線程的優(yōu)先級(jí)數(shù)值(線程是有優(yōu)先級(jí)別的)。
- Thread.sleep():將當(dāng)前線程睡眠指定毫秒數(shù)沽瞭。
- join():調(diào)用某線程的該方法迁匠,將當(dāng)前線程與該線程合并,也即等待該線程結(jié)束后驹溃,再恢復(fù)當(dāng)前線程的運(yùn)行狀態(tài)(比如在線程B中調(diào)用了線程A的 join()城丧,直到線程A執(zhí)行完畢后,才會(huì)繼續(xù)執(zhí)行線程B)豌鹤。
- yield():當(dāng)前線程讓出 CPU亡哄,進(jìn)入就緒狀態(tài),等待 CPU 的再次調(diào)度布疙。
- wait():當(dāng)前線程進(jìn)入對(duì)象的 wait pool蚊惯。
- notify()/notifyAll():?jiǎn)拘褜?duì)象的 wait pool 中的一個(gè)/所有的等待線程。
三灵临、isAlive():判定線程是否處于活動(dòng)狀態(tài)
//判定線程是否處于活動(dòng)狀態(tài):就緒狀態(tài)截型、運(yùn)行狀態(tài)、阻塞狀態(tài)
public class Demo2 {
public static void main(String[] args) {
new ThreadB().start();
}
}
class ThreadB extends Thread {
@Override
public void run() {
System.out.println("檢測(cè)線程是否是活動(dòng)狀態(tài)");
System.out.println(Thread.currentThread().isAlive());
}
}
四儒溉、setPriority():設(shè)置線程的優(yōu)先級(jí)
//設(shè)置線程優(yōu)先級(jí) MAX_PRIORITY:最大為10
//MIN_PRIORITY:最小為1
//DEFAULT_PRIORITY:默認(rèn)為5
public class Demo4 {
public static void main(String[] args) {
ThreadD td = new ThreadD();
ThreadE te = new ThreadE();
td.start();// 誰先啟動(dòng)宦焦,誰搶占資源的概率越大
te.start();
td.setPriority(Thread.MAX_PRIORITY);// 設(shè)置優(yōu)先級(jí)為最大,10
te.setPriority(Thread.MIN_PRIORITY);// 設(shè)置優(yōu)先級(jí)為最小顿涣,1
}
}
class ThreadD extends Thread {
@Override
public void run() {
// 默認(rèn)優(yōu)先級(jí)為5
// System.out.println(Thread.currentThread().getPriority());
for (int i = 0; i < 10; i++) {
System.out.println("ThreadD j---" + i);
}
}
}
class ThreadE extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadE i---" + i);
}
}
}
ThreadD j---0
ThreadE i---0
ThreadE i---1
ThreadE i---2
ThreadE i---3
ThreadE i---4
ThreadE i---5
ThreadE i---6
ThreadE i---7
ThreadE i---8
ThreadE i---9
ThreadD j---1
ThreadD j---2
ThreadD j---3
ThreadD j---4
ThreadD j---5
ThreadD j---6
ThreadD j---7
ThreadD j---8
ThreadD j---9
五波闹、sleep():將當(dāng)前線程睡眠指定毫秒數(shù)
可以調(diào)用 Thread 的靜態(tài)方法:
public static void sleep(long millis) throws InterruptedException
在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時(shí)器和調(diào)度程序精度和準(zhǔn)確性的影響涛碑,該線程不丟失任何監(jiān)視器的所屬權(quán)精堕。InterruptedException - 如果任何線程中斷了當(dāng)前線程,當(dāng)拋出該異常時(shí)锌唾,當(dāng)前線程的中斷狀態(tài)被清除锄码。由于是靜態(tài)方法夺英,sleep() 可以由類名直接調(diào)用:
Thread.sleep(.....);
class TestThread {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
try { // 在哪個(gè)線程中調(diào)用 Sleep,就讓哪個(gè)線程睡眠
Thread.sleep(8000); // 主線程睡8秒后滋捶,打斷子線程
} catch (InterruptedException e) {
}
thread.interrupt(); // 打斷子線程
}
}
class MyThread extends Thread {
@Override
public void run() {
while(true){
System.out.println("=== "+ new Date()+" ===");
try {
sleep(1000); // 每隔一秒打印一次日期
} catch (InterruptedException e) {
return;
}
}
}
}
運(yùn)行結(jié)果:
=== Tue May 09 11:09:43 CST 2017 ===
=== Tue May 09 11:09:44 CST 2017 ===
=== Tue May 09 11:09:45 CST 2017 ===
=== Tue May 09 11:09:46 CST 2017 ===
=== Tue May 09 11:09:47 CST 2017 ===
=== Tue May 09 11:09:48 CST 2017 ===
=== Tue May 09 11:09:49 CST 2017 ===
=== Tue May 09 11:09:50 CST 2017 ===
子線程每隔一秒打印系統(tǒng)日期痛悯,主線程睡眠8秒后,打斷子線程重窟,子線程結(jié)束载萌。
在本例中,采用一種簡(jiǎn)單巡扇、粗暴扭仁、好用的方法中斷子線程:
class TestThread {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
try { // 在哪個(gè)線程中調(diào)用 Sleep,就讓哪個(gè)線程睡眠
Thread.sleep(5000); // 主線程睡8秒后厅翔,打斷子線程
} catch (InterruptedException e) {
}
thread.flag = false; // 打斷子線程
}
}
class MyThread extends Thread {
boolean flag = true;
@Override
public void run() {
while(flag){
System.out.println("=== "+ new Date()+" ===");
try {
sleep(1000); // 每隔一秒打印一次日期
} catch (InterruptedException e) {
}
}
}
}
運(yùn)行結(jié)果:
=== Tue May 09 12:21:24 CST 2017 ===
=== Tue May 09 12:21:25 CST 2017 ===
=== Tue May 09 12:21:26 CST 2017 ===
=== Tue May 09 12:21:27 CST 2017 ===
=== Tue May 09 12:21:28 CST 2017 ===
六乖坠、join():合并某個(gè)線程,相當(dāng)于方法的調(diào)用
class TestThread {
public static void main(String[] args) {
MyThread myThread = new MyThread("childThread");
myThread.start();
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 1; i <= 4; i++){
System.out.println("I am the mainThread");
}
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i = 1; i <= 4; i++){
System.out.println("I am " + getName());
try {
sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
}
運(yùn)行結(jié)果:
I am childThread
I am childThread
I am childThread
I am childThread
I am the mainThread
I am the mainThread
I am the mainThread
I am the mainThread
等待線程結(jié)束后刀闷,再恢復(fù)當(dāng)前線程的運(yùn)行熊泵。
七、yield():讓出 CPU甸昏,當(dāng)前線程進(jìn)入就緒狀態(tài)隊(duì)列等待顽分,給其他線程執(zhí)行的機(jī)會(huì)(就讓很小的一個(gè)時(shí)間片段)。
class TestThread {
public static void main(String[] args) {
MyThread my1 = new MyThread("t1");
MyThread my2 = new MyThread("t2");
my1.start();
my2.start();
}
}
class MyThread extends Thread {
public MyThread(String s) {
super(s);
}
@Override
public void run() {
for(int i = 1; i <= 100; i++){
System.out.println(getName()+":"+i);
if(i % 10 == 0) {
Thread.yield(); // 當(dāng)前線程讓出 CPU 一小會(huì)兒
}
}
}
}
八施蜜、wait():它會(huì)釋放掉對(duì)象的鎖
當(dāng)前的線程必須擁有當(dāng)前對(duì)象的 monitor卒蘸,也即 lock,就是鎖翻默。線程調(diào)用 wait()缸沃,釋放它對(duì)鎖的擁有權(quán),使得當(dāng)前線程必須要等待冰蘑。然后等待另外的線程調(diào)用 notify() 或者 notifyAll() 來通知它和泌,這樣它才能重新獲得鎖的擁有權(quán)和恢復(fù)執(zhí)行。要確保調(diào)用 wait() 的時(shí)候擁有鎖祠肥,即:wait() 的調(diào)用必須放在 synchronized 方法或 synchronized 塊中武氓。
區(qū)別:
1??當(dāng)線程調(diào)用了 wait() 時(shí),它會(huì)釋放掉對(duì)象的鎖仇箱。
2??另一個(gè)會(huì)導(dǎo)致線程暫停的方法:Thread.sleep()县恕,它會(huì)導(dǎo)致線程睡眠指定的毫秒數(shù),但在睡眠的過程中是不會(huì)釋放掉對(duì)象的鎖的剂桥。
3??wait() 和 notify()/notifyAll() 在釋放對(duì)象鎖的區(qū)別在于:wait() 立即釋放忠烛,notify()/notifyAll() 則會(huì)等待線程剩余代碼執(zhí)行完畢才會(huì)釋放。
九权逗、notify()/notifyAll():?jiǎn)拘褜?duì)象的 wait pool 中的一個(gè)/所有等待線程
notify() 會(huì)喚醒一個(gè)等待當(dāng)前對(duì)象的鎖的線程美尸。如果多個(gè)線程在等待冤议,它們中的一個(gè)將會(huì)選擇被喚醒。這種選擇是隨意的师坎,和具體實(shí)現(xiàn)有關(guān)(線程等待一個(gè)對(duì)象的鎖是由于調(diào)用了 wait() 中的一個(gè))恕酸。被喚醒的線程是不能被執(zhí)行的,需要等到當(dāng)前線程放棄這個(gè)對(duì)象的鎖胯陋。被喚醒的線程將和其他線程以通常的方式進(jìn)行競(jìng)爭(zhēng)蕊温,來獲得對(duì)象的鎖。也就是說遏乔,被喚醒的線程并沒有什么優(yōu)先權(quán)义矛,也沒有什么劣勢(shì),對(duì)象的下一個(gè)線程還是需要通過一般性的競(jìng)爭(zhēng)盟萨。
notify() 應(yīng)該是被擁有對(duì)象的鎖的線程所調(diào)用This method should only be called by a thread that is the owner of this object's monitor
凉翻。換句話說,和 wait() 一樣鸯旁,notify() 調(diào)用必須放在 synchronized 方法或 synchronized 塊中噪矛。
問:為什么wait()和notify()/notifyAll()要在同步塊中被調(diào)用量蕊?
這是JDK強(qiáng)制的铺罢,wait() 和 notify()/notifyAll() 在調(diào)用前都必須先獲得對(duì)象的鎖。
十残炮、一個(gè)線程變?yōu)橐粋€(gè)對(duì)象的鎖的擁有者的三種方法
- 執(zhí)行這個(gè)對(duì)象的 synchronized 實(shí)例方法韭赘。
- 執(zhí)行這個(gè)對(duì)象的 synchronized 語句塊。這個(gè)語句塊鎖的是這個(gè)對(duì)象势就。如 synchronized(object)
- 對(duì)于 Class 類的對(duì)象泉瞻,執(zhí)行那個(gè)類的 synchronized、static 方法苞冯。
十一袖牙、Daemon Thread(守護(hù)線程)
Java 中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護(hù)線程)舅锄。用戶線程即運(yùn)行在前臺(tái)的線程鞭达,而守護(hù)線程是運(yùn)行在后臺(tái)的線程。守護(hù)線程的作用是為其他前臺(tái)線程的運(yùn)行提供便利服務(wù)皇忿,而且僅在普通畴蹭、非守護(hù)線程仍然運(yùn)行時(shí)才需要,比如垃圾回收線程就是一個(gè)守護(hù)線程鳍烁。當(dāng) JVM 檢測(cè)僅剩一個(gè)守護(hù)線程叨襟,而用戶線程都已經(jīng)退出運(yùn)行時(shí),JVM 就會(huì)退出幔荒,因?yàn)闆]有了被守護(hù)者糊闽,也就沒有繼續(xù)運(yùn)行程序的必要了梳玫。如果有非守護(hù)線程仍然存活,JVM 就不會(huì)退出右犹。
守護(hù)線程必須在它的 start() 之前通過 setDaemon(true) 進(jìn)行設(shè)置汽纠。
//守護(hù)線程:在線程啟動(dòng)前設(shè)置setDaemon(true)
public class Demo6 {
public static void main(String[] args) throws Exception {
ThreadH th = new ThreadH();
th.setDaemon(true);// 讓當(dāng)前線程設(shè)置為守護(hù)線程
th.start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println("主線程。傀履。虱朵。" + i);
}
}
}
class ThreadH extends Thread {
int i;
@Override
public void run() {
while (true) {
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Hello,---" + ++i);
}
}
}
十二、如何檢測(cè)一個(gè)線程是否持有對(duì)象鎖
Thread 類提供了一個(gè) holdsLock(Object obj)钓账,當(dāng)且僅當(dāng)對(duì)象 obj 的監(jiān)視器被某條線程持有的時(shí)候才會(huì)返回 true碴犬。這是一個(gè) static 方法,這意味著“某條線程”指的是當(dāng)前線程梆暮。
十三服协、如何喚醒一個(gè)阻塞的線程
如果線程是因?yàn)檎{(diào)用了 wait()、sleep() 或者 join() 而導(dǎo)致的阻塞啦粹,可以中斷線程偿荷,并且通過拋出 InterruptedException 來喚醒它。如果線程遇到了 IO 阻塞唠椭,無能為力跳纳,因?yàn)?IO 是操作系統(tǒng)實(shí)現(xiàn)的,Java 代碼并沒有辦法直接接觸到操作系統(tǒng)贪嫂。
十四寺庄、Thread 類中的 start() 和 run() 的區(qū)別
1??start() 來啟動(dòng)線程,真正實(shí)現(xiàn)了多線程運(yùn)行力崇。線程類的 start() 可以用來啟動(dòng)線程斗塘;該方法會(huì)在內(nèi)部調(diào)用 Runnable 接口的 run(),以在單獨(dú)的線程中執(zhí)行 run() 中指定的代碼亮靴。這時(shí)無需等待 run 方法體代碼執(zhí)行完畢馍盟,可以直接繼續(xù)執(zhí)行下面的代碼。通過調(diào)用 Thread 類的 start() 來啟動(dòng)一個(gè)線程茧吊,這時(shí)此線程是處于就緒狀態(tài)贞岭,并沒有運(yùn)行。然后通過此 Thread 類調(diào)用 run() 來完成其運(yùn)行操作的饱狂,這里 run() 稱為線程體曹步,它包含了要執(zhí)行的這個(gè)線程的內(nèi)容,run() 運(yùn)行結(jié)束休讳,此線程終止讲婚。然后 CPU 再調(diào)度其它線程。start() 啟動(dòng)線程執(zhí)行以下任務(wù):
● 它統(tǒng)計(jì)了一個(gè)新線程
● 線程從 New State 移動(dòng)到 Runnable 狀態(tài)俊柔。
● 當(dāng)線程有機(jī)會(huì)執(zhí)行時(shí)筹麸,它的目標(biāo) run() 將運(yùn)行活合。
2??run() 當(dāng)作普通方法的方式調(diào)用。程序還是要順序執(zhí)行物赶,要等待 run 方法體執(zhí)行完畢后白指,才可繼續(xù)執(zhí)行下面的代碼;程序中只有主線程——這一個(gè)線程酵紫,其程序執(zhí)行路徑還是只有一條告嘲,這樣就沒有達(dá)到寫線程的目的。
調(diào)用 start() 才可啟動(dòng)線程奖地,而 run() 只是 thread 的一個(gè)普通方法調(diào)用橄唬,還是在主線程里執(zhí)行。把需要并行處理的代碼放在 run() 中参歹,start() 啟動(dòng)線程將自動(dòng)調(diào)用 run() 仰楚,這是由JVM的內(nèi)存機(jī)制規(guī)定的。并且 run() 必須是 public 訪問權(quán)限犬庇,返回值類型為 void僧界。
3??區(qū)別
①方法的定義
start() 在java.lang.Thread類中定義;而 run() 在java.lang.Runnable接口中定義臭挽,必須在實(shí)現(xiàn)類中重寫捂襟。
②新線程創(chuàng)建
當(dāng)程序調(diào)用 start() 時(shí),才會(huì)表現(xiàn)出多線程的特性埋哟,此時(shí)創(chuàng)建一個(gè)新線程笆豁,然后執(zhí)行 run()。如果直接調(diào)用 run()赤赊,則不會(huì)創(chuàng)建新的線程,run() 將作為當(dāng)前調(diào)用線程本身的常規(guī)方法調(diào)用執(zhí)行煞赢,并且不會(huì)發(fā)生多線程抛计。示例:
class MyThread extends Thread {
public void run() {
System.out.println("\n");
System.out.println("當(dāng)前線程的名稱: "
+ Thread.currentThread().getName());
System.out.println("run()方法調(diào)用");
}
}
class demo {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
當(dāng)調(diào)用線程類實(shí)例的 start() 時(shí),會(huì)創(chuàng)建一個(gè)新的線程照筑,默認(rèn)名稱為 Thread-0吹截,然后調(diào)用 run(),并在其中執(zhí)行所有內(nèi)容凝危。新創(chuàng)建的線程波俄。
現(xiàn)在,嘗試直接調(diào)用 run() 而不是 start():
class MyThread extends Thread {
public void run() {
System.out.println("\n");
System.out.println("當(dāng)前線程的名稱: "
+ Thread.currentThread().getName());
System.out.println("run()方法調(diào)用");
}
}
class GeeksforGeeks {
public static void main(String[] args) {
MyThread t = new MyThread();
t.run();
}
}
當(dāng)調(diào)用 MyThread 類的 run() 時(shí)蛾默,沒有創(chuàng)建新線程懦铺,并且在當(dāng)前線程即主線程上執(zhí)行run()。因此支鸡,沒有發(fā)生多線程冬念。run() 是作為正常函數(shù)被調(diào)用趁窃。
③多次調(diào)用
start() 不能多次調(diào)用,否則拋出java.lang.IllegalStateException急前。run() 可以進(jìn)行多次調(diào)用醒陆,因?yàn)樗皇且环N正常的方法調(diào)用。