Java 給多線程編程提供了內(nèi)置的支持贤笆。 一條線程指的是進(jìn)程中一個(gè)單一順序的控制流蝇棉,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)芥永。
多線程是多任務(wù)的一種特別的形式篡殷,但多線程使用了更小的資源開銷。多線程能滿足程序員編寫高效率的程序來達(dá)到充分利用 CPU 的目的埋涧。
這里定義和線程相關(guān)的另一個(gè)術(shù)語(yǔ) - 進(jìn)程:一個(gè)進(jìn)程包括由操作系統(tǒng)分配的內(nèi)存空間板辽,包含一個(gè)或多個(gè)線程。一個(gè)線程不能獨(dú)立的存在棘催,它必須是進(jìn)程的一部分劲弦。一個(gè)進(jìn)程一直運(yùn)行,直到所有的非守護(hù)線程都結(jié)束運(yùn)行后才能結(jié)束醇坝。
一個(gè)線程的生命周期
線程是一個(gè)動(dòng)態(tài)執(zhí)行的過程邑跪,它也有一個(gè)從產(chǎn)生到死亡的過程。下圖顯示了一個(gè)線程完整的生命周期:
新建狀態(tài):使用 new 關(guān)鍵字和 Thread 類或其子類建立一個(gè)線程對(duì)象后呼猪,該線程對(duì)象就處于新建狀態(tài)画畅。它保持這個(gè)狀態(tài)直到程序 start() 這個(gè)線程。
就緒狀態(tài):當(dāng)線程對(duì)象調(diào)用了start()方法之后郑叠,該線程就進(jìn)入就緒狀態(tài)夜赵。就緒狀態(tài)的線程處于就緒隊(duì)列中,要等待JVM里線程調(diào)度器的調(diào)度乡革。
運(yùn)行狀態(tài):如果就緒狀態(tài)的線程獲取 CPU 資源寇僧,就可以執(zhí)行 run()摊腋,此時(shí)線程便處于運(yùn)行狀態(tài)。處于運(yùn)行狀態(tài)的線程最為復(fù)雜嘁傀,它可以變?yōu)樽枞麪顟B(tài)兴蒸、就緒狀態(tài)和死亡狀態(tài)。
阻塞狀態(tài):如果一個(gè)線程執(zhí)行了sleep(睡眠)细办、suspend(掛起)等方法橙凳,失去所占用資源之后,該線程就從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)笑撞。在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)岛啸。可以分為三種:
- 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait() 方法茴肥,使線程進(jìn)入到等待阻塞狀態(tài)坚踩。
- 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)橥芥i被其他線程占用)。
- 其他阻塞:通過調(diào)用線程的 sleep() 或 join() 發(fā)出了 I/O 請(qǐng)求時(shí)瓤狐,線程就會(huì)進(jìn)入到阻塞狀態(tài)瞬铸。當(dāng)sleep() 狀態(tài)超時(shí),join() 等待線程終止或超時(shí)础锐,或者 I/O 處理完畢嗓节,線程重新轉(zhuǎn)入就緒狀態(tài)。
死亡狀態(tài): 一個(gè)運(yùn)行狀態(tài)的線程完成任務(wù)或者其他終止條件發(fā)生時(shí)皆警,該線程就切換到終止?fàn)顟B(tài)拦宣。
線程的優(yōu)先級(jí)
每一個(gè) Java 線程都有一個(gè)優(yōu)先級(jí)鬼雀,線程的優(yōu)先級(jí)有助于操作系統(tǒng)確定線程的調(diào)度順序硅则。
Java 線程的優(yōu)先級(jí)是一個(gè)整數(shù)讶凉,其取值范圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )汛骂。默認(rèn)情況下遗菠,每一個(gè)線程都會(huì)分配一個(gè)優(yōu)先級(jí) NORM_PRIORITY(5)笛厦。
具有較高優(yōu)先級(jí)的線程對(duì)程序更重要维费,并且應(yīng)該在低優(yōu)先級(jí)的線程之前分配處理器資源种蝶。但是左痢,線程優(yōu)先級(jí)并不能保證線程執(zhí)行的順序靡羡,而且非常依賴于平臺(tái)。
創(chuàng)建一個(gè)線程的方法
Java 提供了三種創(chuàng)建線程的方法:
- 通過實(shí)現(xiàn) Runnable 接口俊性;
- 通過繼承 Thread 類本身略步;
- 通過 Callable 和 Future 創(chuàng)建線程。
通過實(shí)現(xiàn) Runnable 接口來創(chuàng)建線程:創(chuàng)建一個(gè)線程定页,最簡(jiǎn)單的方法是創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類趟薄。為了實(shí)現(xiàn) Runnable,一個(gè)類只需要執(zhí)行一個(gè)方法調(diào)用 run()典徊,聲明如下:
public void run()
可以重寫該方法杭煎,重要的是理解的 run() 可以調(diào)用其他方法恩够,使用其他類,并聲明變量羡铲,就像主線程一樣蜂桶。
在創(chuàng)建一個(gè)實(shí)現(xiàn) Runnable 接口的類之后,可以在類中實(shí)例化一個(gè)線程對(duì)象也切。
Thread 定義了幾個(gè)構(gòu)造方法扑媚,下面的這個(gè)是經(jīng)常使用的:
// threadOb 是一個(gè)實(shí)現(xiàn) Runnable 接口的類的實(shí)例,并且 threadName 指定新線程的名字
Thread(Runnable threadOb,String threadName);
新線程創(chuàng)建之后雷恃,調(diào)用它的 start() 方法它才會(huì)運(yùn)行疆股。聲明如下:
void start();
下面是一個(gè)創(chuàng)建線程并開始讓它執(zhí)行的實(shí)例:
public class RunnableDemo implements Runnable{
// 線程對(duì)象
private Thread t;
// 線程名稱
private String threadName;
// 構(gòu)造方法
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
// 實(shí)現(xiàn) run() 抽象方法
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 讓線程睡眠一會(huì)
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
// 運(yùn)行線程
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
// 線程開始執(zhí)行
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo( "Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo( "Thread-2");
R2.start();
}
}
// 程序運(yùn)行結(jié)果如下:
// Creating Thread-1
// Starting Thread-1
// Creating Thread-2
// Starting Thread-2
// Running Thread-1
// Thread: Thread-1, 4
// Running Thread-2
// Thread: Thread-2, 4
// Thread: Thread-1, 3
// Thread: Thread-2, 3
// Thread: Thread-1, 2
// Thread: Thread-2, 2
// Thread: Thread-1, 1
// Thread: Thread-2, 1
// Thread Thread-2 exiting.
// Thread Thread-1 exiting.
// Process finished with exit code 0
通過繼承Thread來創(chuàng)建線程:創(chuàng)建一個(gè)線程的第二種方法是創(chuàng)建一個(gè)新的類,該類繼承 Thread 類褂萧,然后創(chuàng)建一個(gè)該類的實(shí)例押桃。繼承類必須重寫 run() 方法,該方法是新線程的入口點(diǎn)导犹。它也必須調(diào)用 start() 方法才能執(zhí)行。通過繼承Thread來創(chuàng)建線程盡管被列為一種多線程實(shí)現(xiàn)方式羡忘,但是本質(zhì)上也是實(shí)現(xiàn)了 Runnable 接口的一個(gè)實(shí)例:
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 讓線程睡眠一會(huì)
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}
// 程序運(yùn)行結(jié)果如下:
// Creating Thread-1
// Starting Thread-1
// Creating Thread-2
// Starting Thread-2
// Running Thread-1
// Thread: Thread-1, 4
// Running Thread-2
// Thread: Thread-2, 4
// Thread: Thread-1, 3
// Thread: Thread-2, 3
// Thread: Thread-2, 2
// Thread: Thread-1, 2
// Thread: Thread-2, 1
// Thread: Thread-1, 1
// Thread Thread-1 exiting.
// Thread Thread-2 exiting.
// Process finished with exit code 0
下表列出了Thread類的一些重要方法(Thread 對(duì)象調(diào)用):
序號(hào) | 方法及說明 |
---|---|
1 | public void start()谎痢,使該線程開始執(zhí)行;Java 虛擬機(jī)調(diào)用該線程的 run 方法 |
2 | public void run()卷雕,如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對(duì)象構(gòu)造的节猿,則調(diào)用該 Runnable 對(duì)象的 run 方法;否則漫雕,該方法不執(zhí)行任何操作并返回 |
3 | public final void setName(String name)滨嘱,改變線程名稱,使之與參數(shù) name 相同 |
4 | public final void setPriority(int priority)浸间,更改線程的優(yōu)先級(jí) |
5 | public final void setDaemon(boolean on)太雨,將該線程標(biāo)記為守護(hù)線程或用戶線程 |
6 | public final void join(long millisec),會(huì)讓運(yùn)行到(不是調(diào)用方)該方法的線程處于阻塞狀態(tài)魁蒜,阻塞的最長(zhǎng)時(shí)間為 millis 毫秒(0或無參會(huì)無限等待)囊扳,知道調(diào)用該方法的線程運(yùn)行完畢或是到達(dá)等待最長(zhǎng)時(shí)間后解除運(yùn)行該方法的線程的阻塞狀態(tài) |
7 | public void interrupt(),中斷一個(gè)正在運(yùn)行的線程兜看,若是該線程正處于某種阻塞時(shí)被調(diào)用中斷方法中斷锥咸,那么并不是將該線程直接中斷,而是中斷其阻塞狀態(tài)细移,這時(shí)通常會(huì)拋出異常搏予,通知程序該線程的阻塞狀態(tài)被打斷 |
8 | public final boolean isAlive(),測(cè)試線程是否處于活動(dòng)狀態(tài) |
下面表格的方法是 Thread 類的靜態(tài)方法:
序號(hào) | 方法及說明 |
---|---|
1 | public static void yield()弧轧,暫停當(dāng)前正在執(zhí)行的線程對(duì)象雪侥,并執(zhí)行其他線程 |
2 | public static void sleep(long millisec)球涛,在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時(shí)器和調(diào)度程序精度和準(zhǔn)確性的影響校镐。 |
3 | public static boolean holdsLock(Object x)亿扁,當(dāng)且僅當(dāng)當(dāng)前線程在指定的對(duì)象上保持監(jiān)視器鎖時(shí),才返回 true |
4 | public static Thread currentThread()鸟廓,返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用 |
5 | public static void dumpStack()从祝,將當(dāng)前線程的堆棧跟蹤打印至標(biāo)準(zhǔn)錯(cuò)誤流 |
通過 Callable 和 Future 創(chuàng)建線程:
- 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn) call() 方法引谜,該 call() 方法將作為線程執(zhí)行體牍陌,并且有返回值。
- 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例员咽,使用 FutureTask 類來包裝 Callable 對(duì)象毒涧,該 FutureTask 對(duì)象封裝了該 Callable 對(duì)象的 call() 方法的返回值。
- 使用 FutureTask 對(duì)象作為 Thread 對(duì)象的 target 創(chuàng)建并啟動(dòng)新線程贝室。
- 調(diào)用 FutureTask 對(duì)象的 get() 方法來獲得子線程執(zhí)行結(jié)束后的返回值契讲。
package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類
public class CallableThreadTest implements Callable {
public static void main(String[] args)
{
// 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來包裝 Callable 對(duì)象
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
// 主線程輸出
System.out.println(Thread.currentThread().getName()+" 的循環(huán)變量i的值"+i);
if(i==20)
{
// 以 FutureTask 對(duì)象 和 新線程的名稱 創(chuàng)建并啟動(dòng)子線程
new Thread(ft,"有返回值的線程").start();
}
}
try
{
// 輸出子線程執(zhí)行結(jié)束后的返回值
System.out.println("子線程的返回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
// 實(shí)現(xiàn) call() 方法滑频,該 call() 方法將作為線程執(zhí)行體捡偏,并且有返回值
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
// 子線程輸出
System.out.println(Thread.currentThread().getName()+" "+i);
}
return I;
}
}
創(chuàng)建線程的三種方式的對(duì)比:
- 采用實(shí)現(xiàn) Runnable、Callable 接口的方式創(chuàng)建多線程時(shí)峡迷,線程類只是實(shí)現(xiàn)了 Runnable 接口或 Callable 接口银伟,還可以繼承其他類。
- 使用繼承 Thread 類的方式創(chuàng)建多線程時(shí)绘搞,編寫簡(jiǎn)單彤避,如果需要訪問當(dāng)前線程,則無需使用 Thread.currentThread() 方法夯辖,直接使用 this 即可獲得當(dāng)前線程琉预。
線程的幾個(gè)主要概念
在多線程編程時(shí),需要了解以下幾個(gè)概念:
- 線程同步
- 線程死鎖
- 線程間通信
- 線程控制:掛起楼雹、停止和恢復(fù)
線程同步:即當(dāng)有一個(gè)線程在對(duì)內(nèi)存進(jìn)行操作時(shí)模孩,其他線程都不可以對(duì)這個(gè)內(nèi)存地址進(jìn)行操作,直到該線程完成操作贮缅, 其他線程才能對(duì)該內(nèi)存地址進(jìn)行操作榨咐。
同步方法:在一個(gè)方法上使用 synchronized 修飾后,那么該方法稱為“同步方法”谴供,即多個(gè)線程不能同時(shí)在方法內(nèi)部執(zhí)行块茁,從而解決并發(fā)安全問題。靜態(tài)方法使用 synchronized 修飾后,那么該方法一定具有同步效果数焊。聲明格式如下:
public synchronized 返回值類型 方法名(){}
同步塊:在需要同步運(yùn)行的代碼片段上使用 synchronized 修飾后永淌,那么該代碼塊稱為“同步塊”,有效的縮小同步范圍可以在保證并發(fā)安全的前提下盡可能的提高并發(fā)效率佩耳。聲明格式如下:
synchronized (同步監(jiān)視器) {
需要同步運(yùn)行的代碼片段
}
同步監(jiān)視器:同步監(jiān)視器可以是 Java 中任意的一個(gè)對(duì)象遂蛀。在“同步方法”中,同步監(jiān)視器對(duì)象就是當(dāng)前方法所屬對(duì)象干厚,即方法內(nèi)部看到的 this李滴。在“同步塊”中,只要保證多個(gè)線程看到的該對(duì)象是‘同一個(gè)’蛮瞄,即可保證同步塊中的代碼是并發(fā)安全的所坯。
使用“同步塊”的實(shí)例:
package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類
public class CallableThreadTest implements Callable {
public static void main(String[] args)
{
// 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例,使用 FutureTask 類來包裝 Callable 對(duì)象
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
FutureTask<Integer> ft2 = new FutureTask<>(ctt);
// 以 FutureTask 對(duì)象 和 新線程的名稱 創(chuàng)建并啟動(dòng)子線程
Thread t1 = new Thread(ft,"有返回值的線程");
t1.start();
Thread t2 = new Thread(ft2,"有返回值的線程2");
t2.start();
}
// 實(shí)現(xiàn) call() 方法挂捅,該 call() 方法將作為線程執(zhí)行體芹助,并且有返回值
@Override
public Integer call() throws Exception
{
synchronized (this){
int i = 0;
for(;i<100;i++)
{
// 子線程輸出
System.out.println(Thread.currentThread().getName()+" "+i);
}
return I;
}
}
}
// 程序運(yùn)行結(jié)果如下:
// 使用同步塊的情況下,一個(gè)線程會(huì)完成循環(huán)后闲先,另一個(gè)線程再開始循環(huán)
// 不實(shí)用同步塊的情況下状土,兩個(gè)線程中的循環(huán)交替進(jìn)行
線程互斥鎖:當(dāng)使用 synchronized 鎖住多段不同的代碼片段,但是這些同步塊使用的同步監(jiān)視器對(duì)象是同一個(gè)時(shí)饵蒂,那么這些代碼片段之間就是互斥的声诸,多個(gè)線程不能同時(shí)執(zhí)行他們。
線程死鎖:當(dāng)多個(gè)線程都持有自己的鎖退盯,但都等對(duì)方先釋放鎖時(shí),就會(huì)出現(xiàn)‘僵持’的情況泻肯,使得所有線程進(jìn)入阻塞狀態(tài)渊迁,這個(gè)現(xiàn)象稱為死鎖現(xiàn)象。
線程間通信:當(dāng)多個(gè)線程共同操作共享的資源時(shí)灶挟,線程間通過某種方式互相告知自己的狀態(tài)琉朽,以避免無效的資源爭(zhēng)奪。線程間通信的方式可以有很多種:等待-通知稚铣、共享內(nèi)存箱叁、管道流。每種方式用不同的方法來實(shí)現(xiàn)惕医。
線程控制:Java提供對(duì)多線程程序的完全控制耕漱。可以開發(fā)一個(gè)多線程程序抬伺,根據(jù)要求完全暫停螟够,恢復(fù)或停止。可以在線程對(duì)象上使用各種靜態(tài)方法來控制它們的行為妓笙。
- 掛起: 線程掛起就是指暫停線程的執(zhí)行(阻塞狀態(tài))若河,掛起時(shí)線程不會(huì)釋放對(duì)象鎖。
- 恢復(fù): 恢復(fù)就是讓暫停的線程得以繼續(xù)執(zhí)行.(返回就緒狀態(tài))寞宫。
- 停止:停止一個(gè)線程時(shí)會(huì)強(qiáng)制結(jié)束線程的執(zhí)行萧福,不管run方法是否執(zhí)行完了,并且還會(huì)釋放這個(gè)線程所持有的所有的鎖對(duì)象辈赋。
死鎖及解決方法
死鎖是這樣一種情形:多個(gè)線程同時(shí)被阻塞鲫忍,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放。由于線程被無限期地阻塞炭庙,因此程序不可能正常終止饲窿。
java 死鎖產(chǎn)生的四個(gè)必要條件:
- 1、互斥使用焕蹄,即當(dāng)資源被一個(gè)線程使用(占有)時(shí)逾雄,別的線程不能使用。
- 2腻脏、不可搶占鸦泳,資源請(qǐng)求者不能強(qiáng)制從資源占有者手中奪取資源,資源只能由資源占有者主動(dòng)釋放永品。
- 3做鹰、請(qǐng)求和保持,即當(dāng)資源請(qǐng)求者在請(qǐng)求其他的資源的同時(shí)保持對(duì)原有資源的占有鼎姐。
- 4钾麸、循環(huán)等待,即存在一個(gè)等待隊(duì)列:P1占有P2的資源炕桨,P2占有P3的資源饭尝,P3占有P1的資源。這樣就形成了一個(gè)等待環(huán)路献宫。
當(dāng)上述四個(gè)條件都成立的時(shí)候钥平,便形成死鎖。當(dāng)然姊途,死鎖的情況下如果打破上述任何一個(gè)條件涉瘾,便可讓死鎖消失。下面用java代碼來模擬一下死鎖的產(chǎn)生:
public class LockTest {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
LockA la = new LockA();
new Thread(la).start();
LockB lb = new LockB();
new Thread(lb).start();
}
}
class LockA implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockA 開始執(zhí)行");
while(true){
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockA 鎖住 obj1");
// 此處等待是給B能鎖住機(jī)會(huì)
Thread.sleep(3000);
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockA 鎖住 obj2");
// 為測(cè)試捷兰,占用了就不放
Thread.sleep(60 * 1000);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockB implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockB 開始執(zhí)行");
while(true){
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockB 鎖住 obj2");
// 此處等待是給A能鎖住機(jī)會(huì)
Thread.sleep(3000);
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockB 鎖住 obj1");
// 為測(cè)試立叛,占用了就不放
Thread.sleep(60 * 1000);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 以上程序執(zhí)行結(jié)果為:
// Fri May 12 12:59:39 CST 2023 LockA 開始執(zhí)行
// Fri May 12 12:59:39 CST 2023 LockB 開始執(zhí)行
// Fri May 12 12:59:39 CST 2023 LockA 鎖住 obj1
// Fri May 12 12:59:39 CST 2023 LockB 鎖住 obj2
由于不恰當(dāng)?shù)氖褂昧随i,且出現(xiàn)同時(shí)鎖住多個(gè)對(duì)象時(shí)寂殉,上例中便發(fā)生了死鎖囚巴,LockA 的 run() 方法執(zhí)行后,率先鎖住 obj1 對(duì)象,之后 LockA 中的線程進(jìn)入休眠彤叉,期間 LockB 的 run() 方法中鎖住 obj2 對(duì)象庶柿。當(dāng) LockA 中的線程睡醒后,準(zhǔn)備去鎖住 obj2 對(duì)象秽浇,結(jié)果發(fā)現(xiàn) obj2 對(duì)象已經(jīng)被 LockB 鎖住浮庐,只能站著等 LockB 撒手。而 LockB 中的線程睡醒后柬焕,準(zhǔn)備去鎖住 obj1 對(duì)象审残,結(jié)果發(fā)現(xiàn) obj1 對(duì)象已經(jīng)被 LockA 鎖住,結(jié)果也只能站著等 LockA 撒手斑举。最終 LockA 與 LockB 都站下了搅轿,程序也就站下了。
為了解決這個(gè)問題富玷,我們可以不使用顯示的去鎖璧坟,而使用信號(hào)量去控制。信號(hào)量可以控制資源能被多少線程訪問赎懦,這里我們指定只能被一個(gè)線程訪問雀鹃,就做到了類似鎖住。而信號(hào)量可以指定去獲取的超時(shí)時(shí)間励两,我們可以根據(jù)這個(gè)超時(shí)時(shí)間黎茎,去做一個(gè)額外處理。對(duì)于無法成功獲取的情況当悔,可以采取重復(fù)嘗試傅瞻,或指定嘗試的次數(shù),也可以馬上退出:
public class UnLockTest {
// 同步觀察對(duì)象
public static String obj1 = "obj1";
public static String obj2 = "obj2";
// 信號(hào)量盲憎,初始許可操作數(shù)為1
public static final Semaphore a1 = new Semaphore(1);
public static final Semaphore a2 = new Semaphore(1);
public static void main(String[] args) {
NewLockA la = new NewLockA();
new Thread(la).start();
NewLockB lb = new NewLockB();
new Thread(lb).start();
}
}
class NewLockA implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + " LockA 開始執(zhí)行");
// 無限次的重復(fù)嘗試獲取鎖俭正,直到成功,對(duì)焙畔,就是這么執(zhí)著
while (true) {
// 判斷信號(hào)量 a1 是否許可使用,等待信號(hào)量 a1 許可的時(shí)間最長(zhǎng) 1 秒
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockA 鎖住 obj1");
// 判斷信號(hào)量 a2 是否許可使用串远,等待信號(hào)量 a2 許可的時(shí)間最長(zhǎng) 1 秒
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockA 鎖住 obj2");
// LockA 同時(shí)鎖住 obj1 與 obj2宏多,這里可以進(jìn)行操作,睡一下
System.out.println("LockA 可以安心操作了");
Thread.sleep(10 * 1000);
// 釋放信號(hào)量
UnLockTest.a1.release();
UnLockTest.a2.release();
// 跳出循環(huán)
break;
}else{
// 信號(hào)量 a2 不可用澡罚、或等待信號(hào)量許可超時(shí)
System.out.println(new Date().toString() + " LockA 鎖 obj2 失敗");
}
}else{
// 信號(hào)量 a1 不可用伸但、或等待信號(hào)量許可超時(shí)
System.out.println(new Date().toString() + " LockA 鎖 obj1 失敗");
}
// 釋放信號(hào)量
UnLockTest.a1.release();
UnLockTest.a2.release();
// 上鎖失敗了,暫停當(dāng)前正在執(zhí)行的線程對(duì)象留搔,并執(zhí)行其他線程
Thread.yield();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class NewLockB implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + " LockB 開始執(zhí)行");
// 無限次的重復(fù)嘗試獲取鎖更胖,直到成功,對(duì),就是這么執(zhí)著
while (true) {
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockB 鎖住 obj2");
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockB 鎖住 obj1");
// LockB 同時(shí)鎖住 obj1 與 obj2却妨,這里可以進(jìn)行操作饵逐,睡一下
System.out.println("LockB 可以安心操作了");
Thread.sleep(10 * 1000);
// 釋放信號(hào)量
UnLockTest.a1.release();
UnLockTest.a2.release();
// 跳出循環(huán)
break;
}else{
System.out.println(new Date().toString() + " LockB 鎖 obj1 失敗");
}
}else{
System.out.println(new Date().toString() + " LockB 鎖 obj2 失敗");
}
// 釋放信號(hào)量
UnLockTest.a1.release();
UnLockTest.a2.release();
// 上鎖失敗了,暫停當(dāng)前正在執(zhí)行的線程對(duì)象彪标,并執(zhí)行其他線程
Thread.yield();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 以上程序執(zhí)行結(jié)果為:
// Fri May 12 21:39:40 CST 2023 LockB 開始執(zhí)行
// Fri May 12 21:39:40 CST 2023 LockA 開始執(zhí)行
// Fri May 12 21:39:40 CST 2023 LockB 鎖住 obj2
// Fri May 12 21:39:40 CST 2023 LockA 鎖住 obj1
// Fri May 12 21:39:41 CST 2023 LockB 鎖 obj1 失敗
// Fri May 12 21:39:41 CST 2023 LockB 鎖住 obj2
// Fri May 12 21:39:41 CST 2023 LockA 鎖 obj2 失敗
// Fri May 12 21:39:41 CST 2023 LockB 鎖住 obj1
// LockB 可以安心操作了
// Fri May 12 21:39:41 CST 2023 LockA 鎖住 obj1
// Fri May 12 21:39:41 CST 2023 LockA 鎖住 obj2
// LockA 可以安心操作了
生產(chǎn)者/消費(fèi)者問題
生產(chǎn)者和消費(fèi)者問題是線程模型中的經(jīng)典問題:生產(chǎn)者和消費(fèi)者在同一時(shí)間段內(nèi)共用同一個(gè)存儲(chǔ)空間倍权,如下圖所示,生產(chǎn)者向空間里存放數(shù)據(jù)捞烟,而消費(fèi)者取用數(shù)據(jù)薄声,如果不加以協(xié)調(diào)可能會(huì)出現(xiàn)以下情況:
- 存儲(chǔ)空間已滿,而生產(chǎn)者占用著它题画,消費(fèi)者等著生產(chǎn)者讓出空間從而取出產(chǎn)品默辨,生產(chǎn)者等著消費(fèi)者消費(fèi)產(chǎn)品,從而向空間中添加產(chǎn)品苍息∷跣遥互相等待,從而發(fā)生死鎖档叔。
以下實(shí)例演示了如何通過線程解決生產(chǎn)者/消費(fèi)者問題:
public class ProducerConsumerTest {
public static void main(String[] args) {
CubbyHole c = new CubbyHole();
Producer p1 = new Producer(c, 1);
p1.setPriority(Thread.MAX_PRIORITY);
p1.start();
Consumer c1 = new Consumer(c, 1);
c1.setPriority(Thread.MIN_PRIORITY);
c1.start();
}
}
/**
* 倉(cāng)庫(kù)類
*/
class CubbyHole {
// 倉(cāng)庫(kù)中的產(chǎn)品桌粉,這里的產(chǎn)品是一個(gè)整數(shù)
private int contents;
// 是否可以在倉(cāng)庫(kù)中取出產(chǎn)品
private boolean available = false;
/**
* 同步方法,在倉(cāng)庫(kù)獲取產(chǎn)品
* @return 產(chǎn)品
*/
public synchronized int get() {
// 如果不可以在倉(cāng)庫(kù)中取出產(chǎn)品衙四,就應(yīng)該等待倉(cāng)庫(kù)放入商品铃肯,所以該對(duì)象上的線程就進(jìn)入無限期的等待
while (available == false) {
try {
wait();
}
catch (InterruptedException e) {
}
}
// 如果可以在倉(cāng)庫(kù)中取出產(chǎn)品,取出商品传蹈,喚醒在該對(duì)象上等待的所有線程押逼,再次標(biāo)記為不可以在倉(cāng)庫(kù)中取出商品
available = false;
notifyAll();
return contents;
}
/**
* 同步方法,向倉(cāng)庫(kù)放入商品
* @param value 商品
*/
public synchronized void put(int value) {
// 如果可以在倉(cāng)庫(kù)中取出產(chǎn)品惦界,那么就不再放入商品挑格,所以該對(duì)象上的線程就進(jìn)入無限期的等待
while (available == true) {
try {
wait();
}
catch (InterruptedException e) {
}
}
// 如果不可以在倉(cāng)庫(kù)中取出產(chǎn)品了,說明倉(cāng)庫(kù)沒貨了沾歪,可以放入商品
contents = value;
// 放入商品后漂彤,標(biāo)記為可以取出產(chǎn)品了
available = true;
// 喚醒在該對(duì)象上等待的所有線程
notifyAll();
}
}
/**
* 生產(chǎn)者類
*/
class Producer extends Thread {
// 倉(cāng)庫(kù)對(duì)象
private CubbyHole cubbyhole;
// 生產(chǎn)者編號(hào)
private int number;
/**
* 生產(chǎn)者構(gòu)造方法
* @param c 倉(cāng)庫(kù)對(duì)象
* @param number 生產(chǎn)者編號(hào)
*/
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 5; i++) {
// 向倉(cāng)庫(kù)放入商品
cubbyhole.put(i);
System.out.println("生產(chǎn)者 #" + this.number + " 向倉(cāng)庫(kù)放入: " + i);
try {
// 睡一下,讓出Cpu時(shí)間片
sleep((int)(Math.random() * 100));
} catch (InterruptedException e) { }
}
}
}
/**
* 消費(fèi)者類
*/
class Consumer extends Thread {
// 倉(cāng)庫(kù)對(duì)象
private CubbyHole cubbyhole;
// 消費(fèi)者編號(hào)
private int number;
/**
* 消費(fèi)者構(gòu)造方法
* @param c 倉(cāng)庫(kù)對(duì)象
* @param number 消費(fèi)者編號(hào)
*/
public Consumer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 5; i++) {
// 在倉(cāng)庫(kù)取出商品
value = cubbyhole.get();
System.out.println("消費(fèi)者 #" + this.number+ " 在倉(cāng)庫(kù)取出: " + value);
}
}
}
// 以上程序執(zhí)行結(jié)果為:
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 0
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 0
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 1
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 1
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 2
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 2
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 3
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 3
// 生產(chǎn)者 #1 向倉(cāng)庫(kù)放入: 4
// 消費(fèi)者 #1 在倉(cāng)庫(kù)取出: 4
多線程的使用
有效利用多線程的關(guān)鍵是理解程序是并發(fā)執(zhí)行而不是串行執(zhí)行的灾搏。例如:程序中有兩個(gè)子系統(tǒng)需要并發(fā)執(zhí)行挫望,這時(shí)候就需要利用多線程編程。通過對(duì)多線程的使用狂窑,可以編寫出非常高效的程序媳板。不過請(qǐng)注意,如果創(chuàng)建太多的線程泉哈,程序執(zhí)行的效率實(shí)際上是降低了蛉幸,而不是提升了破讨。因?yàn)樯舷挛牡那袚Q開銷也很重要,如果創(chuàng)建了太多的線程奕纫,CPU 花費(fèi)在上下文的切換的時(shí)間將多于執(zhí)行程序的時(shí)間提陶!
線程池
線程池是一個(gè)容納多個(gè)線程的容器,可以實(shí)現(xiàn)線程的復(fù)用若锁,避免了反復(fù)創(chuàng)建線程的資源消耗搁骑。線程池的主要作用如下:
- 控制線程數(shù)量:每個(gè)線程都會(huì)占用進(jìn)程的一部分內(nèi)存,線程的數(shù)量過多會(huì)導(dǎo)致資源消耗大又固,由于所有的線程都是并發(fā)運(yùn)行仲器,那么過多的線程也會(huì)導(dǎo)致CPU過度切換,導(dǎo)致并發(fā)效率變差仰冠。
- 重用線程:頻繁的創(chuàng)建銷毀線程會(huì)給線程調(diào)度帶來負(fù)擔(dān)乏冀,所以也應(yīng)當(dāng)重用線程。
可以使用 Runnable 接口創(chuàng)建線程池:
public class ThreadPool {
public static void main(String[] args) {
// 創(chuàng)建任務(wù)
TaskRunnable task = new TaskRunnable();
// 創(chuàng)建固定大小的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// 將任務(wù)指派給線程池
// execute只能提交Runnable類型的任務(wù)洋只,沒有返回值
threadPool.execute(task);
threadPool.execute(task);
threadPool.execute(task);
// 有序的停止線程池中的任務(wù)辆沦,在 awaitTermination() 方法中等待子線程任務(wù)完成后,停止線程池
threadPool.shutdown();
//等待直到所有任務(wù)完成
try {
System.out.println("等待子線程完成任務(wù)识虚。肢扯。。");
// 阻塞担锤,等待子線程任務(wù)完成蔚晨,或達(dá)到超時(shí)時(shí)間,解除阻塞
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任務(wù)都完成了肛循,退出程序铭腕!");
}
}
/**
* 任務(wù)類
*/
class TaskRunnable implements Runnable {
@Override
public void run() {
System.out.println("子線程任務(wù)開始了。多糠。累舷。");
try {
Thread.sleep(2000);
} catch (InterruptedException x) {
System.out.println(x);
}
System.out.println("子線程任務(wù)完成了!");
}
}
// 以上程序執(zhí)行結(jié)果為:
// 等待子線程完成任務(wù)。夹孔。被盈。
// 子線程任務(wù)開始了。搭伤。害捕。
// 子線程任務(wù)開始了。闷畸。。
// 子線程任務(wù)完成了!
// 子線程任務(wù)開始了吞滞。佑菩。盾沫。
// 子線程任務(wù)完成了!
// 子線程任務(wù)完成了!
// 任務(wù)都完成了,退出程序殿漠!
使用Callable接口創(chuàng)建線程池:
public class ThreadPool {
public static void main(String[] args) {
// 創(chuàng)建任務(wù)
TaskCallable task = new TaskCallable("future 任務(wù)");
TaskCallable task2 = new TaskCallable("普通任務(wù)");
// 創(chuàng)建固定大小的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
// submit 可以提交 Callable 與 Runnable 類型的任務(wù)赴精,有返回值
Future future = threadPool.submit(task);
threadPool.submit(task2);
threadPool.submit(task2);
threadPool.submit(task2);
try {
System.out.println("阻塞,至少等待 future 任務(wù)完成");
// 阻塞線程绞幌,等待結(jié)果
future.get();
}catch (Exception e) {
System.out.println(e);
}
// 盡力終止線程池執(zhí)行任務(wù)(不保證一定終止)蕾哟,通過Thread.interrupt終止
// 例如線程休眠的話,該方法不會(huì)在 awaitTermination() 方法中等待
threadPool.shutdownNow();
// 等待直到所有任務(wù)完成莲蜘,這里 shutdownNow() 方法不會(huì)在這里等待谭确, 所以無效的
try {
System.out.println("等待子線程完成任務(wù)。票渠。逐哈。");
// 阻塞,等待子線程任務(wù)完成问顷,或達(dá)到超時(shí)時(shí)間昂秃,解除阻塞
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任務(wù)都完成了,退出程序杜窄!");
}
}
/**
* 任務(wù)類
*/
class TaskCallable implements Callable {
private String taskName;
TaskCallable(String taskName) {
this.taskName = taskName;
}
@Override
public Object call() throws Exception {
System.out.println(this.taskName + "開始了肠骆。。塞耕。");
try {
Thread.sleep(2000);
System.out.println(this.taskName + "完成了!");
} catch (InterruptedException x) {
System.out.println(this.taskName + "被打斷了!!");
}
return null;
}
}
// 以上程序執(zhí)行結(jié)果為:
// future 任務(wù)開始了蚀腿。。荷科。
// 阻塞唯咬,至少等待 future 任務(wù)完成
// 普通任務(wù)開始了。畏浆。胆胰。
// future 任務(wù)完成了!
// 普通任務(wù)完成了!
// 普通任務(wù)開始了。刻获。蜀涨。
// 普通任務(wù)開始了。蝎毡。厚柳。
// 等待子線程完成任務(wù)。沐兵。别垮。
// 普通任務(wù)被打斷了!!
// 普通任務(wù)被打斷了!!
// 任務(wù)都完成了,退出程序扎谎!
isAlive() 方法:
public final boolean isAlive()
描述:
測(cè)試線程是否處于活動(dòng)狀態(tài)碳想。如果線程已啟動(dòng)烧董,但尚未終止,則它是活動(dòng)的胧奔。
參數(shù):
無
返回值:
如果此線程是活動(dòng)的逊移,則為true;否則為false龙填。
public class TestThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
printMsg();
}
}
/**
* 打印當(dāng)前線程
*/
public void printMsg() {
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println("name=" + name);
}
public static void main(String[] args) {
TestThread tt = new TestThread();
tt.setName("Thread");
System.out.println("執(zhí)行 start()方法 之前, tt.isAlive()=" + tt.isAlive());
tt.start();
System.out.println("執(zhí)行 start()方法 之后, tt.isAlive()=" + tt.isAlive());
for (int i = 0; i < 5; i++) {
tt.printMsg();
}
System.out.println("main() 方法結(jié)束, tt.isAlive()=" + tt.isAlive());
}
}
// 以上程序執(zhí)行結(jié)果為:
// 執(zhí)行 start()方法 之前, tt.isAlive()=false
// 執(zhí)行 start()方法 之后, tt.isAlive()=true
// name=main
// name=main
// name=main
// name=main
// name=main
// main() 方法結(jié)束, tt.isAlive()=true
// name=Thread
// name=Thread
// name=Thread
// name=Thread
// name=Thread
getState() 方法:
public State getState()
描述:
返回此線程的狀態(tài)胳泉。此方法設(shè)計(jì)用于監(jiān)測(cè)系統(tǒng)狀態(tài),而不是用于同步控制岩遗。
參數(shù):
無
返回值:
這個(gè)線程的狀態(tài)扇商。
注意:
線程狀態(tài)分為:
- NEW -- 尚未啟動(dòng)的線程的線程狀態(tài)。
- RUNNABLE -- 可運(yùn)行線程的線程狀態(tài)喘先。處于可運(yùn)行狀態(tài)的線程正在Java虛擬機(jī)中執(zhí)行钳吟,但它可能正在等待來自操作系統(tǒng)(如處理器)的其他資源。
- BLOCKED -- 被阻止等待監(jiān)視器鎖定的線程的線程狀態(tài)窘拯。處于阻塞狀態(tài)的線程正在等待監(jiān)視器鎖進(jìn)入同步的塊/方法红且,或者在調(diào)用 Object.wait 后重新進(jìn)入同步塊/方法。
- WAITING -- 等待線程的線程狀態(tài)涤姊。由于調(diào)用以下方法之一暇番,線程處于等待狀態(tài):未設(shè)置超時(shí)時(shí)間的 Object.wait()、未設(shè)置超時(shí)時(shí)間的 Thread.join()思喊、LockSupport.park壁酬。處于等待狀態(tài)的線程正在等待另一個(gè)線程執(zhí)行特定操作。
- TIMED_WAITING -- 具有指定等待時(shí)間的等待線程的線程狀態(tài)恨课。由于使用指定的正等待時(shí)間調(diào)用以下方法之一舆乔,線程處于定時(shí)等待狀態(tài):Thread.sleep、設(shè)置超時(shí)時(shí)間的 Object.wait()剂公、設(shè)置超時(shí)時(shí)間的 Thread.join()希俩、LockSupport.parkNanos、
LockSupport.parkUntil纲辽。 - TERMINATED -- 已終止線程的線程狀態(tài)颜武。線程已完成執(zhí)行。
public class TestThread extends Thread {
boolean waiting= true;
boolean ready= false;
TestThread() {
}
public void run() {
String thrdName = Thread.currentThread().getName();
System.out.println(thrdName + " starting.");
while(waiting) {
System.out.println("waiting:" + waiting);
}
System.out.println("waiting...");
startWait();
try {
Thread.sleep(1000);
} catch(Exception exc) {
System.out.println(thrdName + " interrupted.");
}
System.out.println(thrdName + " 停止.");
}
/**
* 開始等待
*/
synchronized void startWait() {
try {
// 讓當(dāng)前線程進(jìn)入等待狀態(tài)
while(!ready) wait();
} catch(InterruptedException exc) {
System.out.println("wait() interrupted");
}
}
/**
* 通知結(jié)束線程等待
*/
synchronized void notice() {
ready = true;
// 喚醒在該對(duì)象上等待的某個(gè)線程
notify();
}
public static void main(String args[]) throws Exception {
TestThread thrd = new TestThread();
thrd.setName("MyThread #1");
// MyThread #1Alive:=false State:=NEW
showThreadStatus(thrd);
thrd.start();
Thread.sleep(50);
// MyThread #1Alive:=true State:=RUNNABLE
showThreadStatus(thrd);
thrd.waiting = false;
Thread.sleep(50);
// MyThread #1Alive:=true State:=WAITING
showThreadStatus(thrd);
thrd.notice();
Thread.sleep(50);
// MyThread #1Alive:=true State:=RUNNABLE
showThreadStatus(thrd);
while(thrd.isAlive()) {
System.out.println("alive");
}
// MyThread #1Alive:=false State:=TERMINATED
showThreadStatus(thrd);
}
/**
* 打印線程狀態(tài)
* @param thrd Thread 對(duì)象
*/
static void showThreadStatus(Thread thrd) {
System.out.println(thrd.getName() + "Alive:=" + thrd.isAlive() + " State:=" + thrd.getState());
}
}
// 以上程序執(zhí)行結(jié)果為:
// MyThread #1Alive:=false State:=NEW
// MyThread #1 starting.
// waiting:true
// 拖吼。鳞上。。
// MyThread #1Alive:=true State:=RUNNABLE
// waiting:true
// waiting:false
// waiting...
// MyThread #1Alive:=true State:=WAITING
// MyThread #1 停止.
// MyThread #1Alive:=false State:=TERMINATED
setPriority() 方法:
public final void setPriority(int newPriority)
描述:
更改此線程的優(yōu)先級(jí)吊档。
參數(shù):
- newPriority -- 將此線程設(shè)置為的優(yōu)先級(jí)篙议。
返回值:
無
public class TestThread extends Thread {
private int countDown = 5;
private volatile double d = 0;
public TestThread(int priority) {
setPriority(priority);
start();
}
public String toString() {
return super.toString() + ": " + countDown;
}
public void run() {
while(true) {
for(int i = 1; i < 100000; i++) {
d = d + (Math.PI + Math.E) / (double) I;
}
System.out.println(this);
if(--countDown == 0) {
return;
}
}
}
public static void main(String[] args) {
TestThread maxThread = new TestThread(Thread.MAX_PRIORITY);
maxThread.setName("maxThread");
for(int i = 0; i < 5; i++) {
TestThread minThreadnew = new TestThread(Thread.MIN_PRIORITY);
minThreadnew.setName("minThreadnew");
}
}
}
join() 方法:
// 該方法有以下幾種語(yǔ)法格式:
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
描述:
運(yùn)行到該方法的代碼線程進(jìn)入阻塞狀態(tài),調(diào)用該方法的線程進(jìn)入運(yùn)行狀態(tài)怠硼,最長(zhǎng)阻塞時(shí)間為 millis 毫秒涡上。
參數(shù):
- millis -- 等待的時(shí)間(以毫秒為單位)趾断。
- nanos -- 額外的納秒等待(范圍0-999999)。設(shè)置后等待時(shí)間為 millis + nanos吩愧。
注意:
如果millis的值為負(fù)數(shù),或者nanos的值不在0-999999的范圍內(nèi)增显,拋出IllegalArgumentException雁佳。
如果有線程中斷了當(dāng)前線程,拋出InterruptedException同云。當(dāng)拋出此異常時(shí)糖权,當(dāng)前線程的中斷狀態(tài)將被清除。
public class TestThread extends Thread {
private int countDown = 2;
private static int threadCount = 0;
private Thread mainThread;
/**
* 構(gòu)造方法炸站,threadCount每次先進(jìn)行自增星澳,作為線程名
*/
public TestThread(Thread mainTThread) {
super("" + ++threadCount);
this.mainThread = mainTThread;
System.out.println("TestThread 的 start() 方法之前,主線程狀態(tài):" + this.mainThread.getState());
start();
}
@Override
public String toString() {
return "#" + getName() + ": " + countDown;
}
@Override
public void run() {
System.out.println("TestThread 的 run() 方法執(zhí)行中旱易,主線程狀態(tài):" + this.mainThread.getState());
while (true) {
System.out.println(this);
if (--countDown == 0)
return;
try {
sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 2; i++) {
// 主線程代碼運(yùn)行到 join() 方法后禁偎,會(huì)將主線程掛起,并等待 TestThread 運(yùn)行完畢后恢復(fù)運(yùn)行
new TestThread(Thread.currentThread()).join();
System.out.println("TestThread 執(zhí)行完成阀坏,主線程狀態(tài):" + Thread.currentThread().getState());
}
}
}
// 以上程序執(zhí)行結(jié)果為:
// TestThread 的 start() 方法之前如暖,主線程狀態(tài):RUNNABLE
// TestThread 的 run() 方法執(zhí)行中,主線程狀態(tài):WAITING
// #1: 2
// #1: 1
// TestThread 執(zhí)行完成忌堂,主線程狀態(tài):RUNNABLE
// TestThread 的 start() 方法之前盒至,主線程狀態(tài):RUNNABLE
// TestThread 的 run() 方法執(zhí)行中,主線程狀態(tài):WAITING
// #2: 2
// #2: 1
// TestThread 執(zhí)行完成士修,主線程狀態(tài):RUNNABLE
interrupt() 方法:
public void interrupt()
描述:
該方法可以中斷一個(gè)正在運(yùn)行的線程枷遂。若是該線程處于某種阻塞時(shí)被調(diào)用中斷方法中斷,那么并不是將該線程直接中斷棋嘲,而是中斷其阻塞狀態(tài)酒唉,這時(shí)通常會(huì)拋出中斷異常,通知應(yīng)用程序該線程的阻塞狀態(tài)被打斷封字。
參數(shù):
無
返回值:
無
public class TestThread extends Thread {
public void run() {
try {
sleep(50000); // 延遲50秒
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] args) throws Exception {
Thread thread = new TestThread();
thread.start();
System.out.println("在50秒之內(nèi)按任意鍵中斷線程!");
System.in.read();
thread.interrupt();
thread.join();
System.out.println("線程已經(jīng)退出!");
}
}
// 以上程序執(zhí)行結(jié)果為:
// 在50秒之內(nèi)按任意鍵中斷線程!
// 3
// sleep interrupted
// 線程已經(jīng)退出!
獲取所有線程:
public class TestThread extends Thread {
public static void main(String[] args) {
TestThread t1 = new TestThread();
t1.setName("thread1");
t1.start();
// 獲取此線程所屬的線程組
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
// 返回此線程組及其子組中活動(dòng)線程數(shù)的估計(jì)值(不準(zhǔn)確)
int noThreads = currentGroup.activeCount();
Thread[] lstThreads = new Thread[noThreads];
// 將此線程組及其子組中的每個(gè)活動(dòng)線程復(fù)制到指定的數(shù)組中
currentGroup.enumerate(lstThreads);
for (int i = 0; i < noThreads; i++) {
System.out.println("線程號(hào):" + i + " = " + lstThreads[i].getName());
}
}
}
// 以上程序執(zhí)行結(jié)果為:
// 線程號(hào):0 = main
// 線程號(hào):1 = Monitor Ctrl-Break
// 線程號(hào):2 = thread1
// 以上程序執(zhí)行結(jié)果也可能為:
// 線程號(hào):0 = main
// 線程號(hào):1 = Monitor Ctrl-Break
getPriority() 方法:
public final int getPriority()
描述:
獲取此線程的優(yōu)先級(jí)黔州。
參數(shù):
無
返回值:
返回此線程的優(yōu)先級(jí)。
public class TestThread {
/**
* 創(chuàng)建 Runnable 實(shí)例
* @return Runnable 實(shí)例
*/
private static Runnable makeRunnable() {
// 使用內(nèi)部類的一種簡(jiǎn)寫向上轉(zhuǎn)型創(chuàng)建 Runnable 實(shí)例
Runnable r = new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
Thread t = Thread.currentThread();
System.out.println("在 run() 方法中 - priority = "
+ t.getPriority()+ ", name = " + t.getName());
try {
Thread.sleep(2000);
}
catch (InterruptedException x) {
}
}
}
};
return r;
}
public static void main(String[] args) {
System.out.println("在 main() 方法中 - Thread.currentThread().getPriority() = " + Thread.currentThread().getPriority());
System.out.println("在 main() 方法中 - Thread.currentThread().getName() = "+ Thread.currentThread().getName());
Thread threadA = new Thread(makeRunnable(), "threadA");
threadA.setPriority(Thread.MAX_PRIORITY);
threadA.start();
try {
Thread.sleep(3000);
}
catch (InterruptedException x) {
}
System.out.println("在 main() 方法中 - threadA.getPriority() = "+ threadA.getPriority());
}
}
// 以上程序執(zhí)行結(jié)果為:
// 在 main() 方法中 - Thread.currentThread().getPriority() = 5
// 在 main() 方法中 - Thread.currentThread().getName() = main
// 在 run() 方法中 - priority = 10, name = threadA
// 在 run() 方法中 - priority = 10, name = threadA
// 在 main() 方法中 - threadA.getPriority() = 10
// 在 run() 方法中 - priority = 10, name = threadA
// 在 run() 方法中 - priority = 10, name = threadA
// 在 run() 方法中 - priority = 10, name = threadA