Java 多線程編程

Java給多線程編程提供了內(nèi)置的支持刷晋。一個多線程程序包含兩個或多個能并發(fā)運(yùn)行的部分冗澈。程序的每一部分都稱作一個線程膊升,并且每個線程定義了一個獨(dú)立的執(zhí)行路徑观蓄。

多線程是多任務(wù)的一種特別的形式址愿。多線程比多任務(wù)需要更小的開銷该镣。

這里定義和線程相關(guān)的另一個術(shù)語:進(jìn)程:一個進(jìn)程包括由操作系統(tǒng)分配的內(nèi)存空間,包含一個或多個線程响谓。一個線程不能獨(dú)立的存在损合,它必須是進(jìn)程的一部分。一個進(jìn)程一直運(yùn)行娘纷,直到所有的非守候線程都結(jié)束運(yùn)行后才能結(jié)束嫁审。

多線程能滿足程序員編寫非常有效率的程序來達(dá)到充分利用CPU的目的,因為CPU的空閑時間能夠保持在最低限度赖晶。

我自己是一個從事了6年的Java全棧工程師律适,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料,從基礎(chǔ)的Java遏插、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識

都有整理哦擦耀,可以來我的主頁免費(fèi)領(lǐng)取哦。

一個線程的生命周期

線程經(jīng)過其生命周期的各個階段涩堤。下圖顯示了一個線程完整的生命周期眷蜓。

新建狀態(tài):?一個新產(chǎn)生的線程從新狀態(tài)開始了它的生命周期。它保持這個狀態(tài)直到程序start這個線程胎围。

運(yùn)行狀態(tài):當(dāng)一個新狀態(tài)的線程被start以后吁系,線程就變成可運(yùn)行狀態(tài),一個線程在此狀態(tài)下被認(rèn)為是開始執(zhí)行其任務(wù)

就緒狀態(tài):當(dāng)一個線程等待另外一個線程執(zhí)行一個任務(wù)的時候白魂,該線程就進(jìn)入就緒狀態(tài)汽纤。當(dāng)另一個線程給就緒狀態(tài)的線程發(fā)送信號時,該線程才重新切換到運(yùn)行狀態(tài)福荸。

休眠狀態(tài):?由于一個線程的時間片用完了蕴坪,該線程從運(yùn)行狀態(tài)進(jìn)入休眠狀態(tài)。當(dāng)時間間隔到期或者等待的事件發(fā)生了敬锐,該狀態(tài)的線程切換到運(yùn)行狀態(tài)背传。

終止?fàn)顟B(tài):?一個運(yùn)行狀態(tài)的線程完成任務(wù)或者其他終止條件發(fā)生,該線程就切換到終止?fàn)顟B(tài)台夺。

線程的優(yōu)先級

每一個Java線程都有一個優(yōu)先級径玖,這樣有助于操作系統(tǒng)確定線程的調(diào)度順序。Java優(yōu)先級在MIN_PRIORITY(1)和MAX_PRIORITY(10)之間的范圍內(nèi)颤介。默認(rèn)情況下梳星,每一個線程都會分配一個優(yōu)先級NORM_PRIORITY(5)赞赖。

具有較高優(yōu)先級的線程對程序更重要,并且應(yīng)該在低優(yōu)先級的線程之前分配處理器時間冤灾。然而前域,線程優(yōu)先級不能保證線程執(zhí)行的順序,而且非常依賴于平臺韵吨。

創(chuàng)建一個線程

Java提供了三種創(chuàng)建線程方法:

通過實現(xiàn)Runnable接口匿垄;

通過繼承Thread類本身;

通過 Callable 和 Future 創(chuàng)建線程学赛。

通過實現(xiàn)Runnable接口來創(chuàng)建線程

創(chuàng)建一個線程年堆,最簡單的方法是創(chuàng)建一個實現(xiàn)Runnable接口的類吞杭。

為了實現(xiàn)Runnable盏浇,一個類只需要執(zhí)行一個方法調(diào)用run(),聲明如下:

public void run()

你可以重寫該方法芽狗,重要的是理解的run()可以調(diào)用其他方法绢掰,使用其他類,并聲明變量童擎,就像主線程一樣滴劲。

在創(chuàng)建一個實現(xiàn)Runnable接口的類之后,你可以在類中實例化一個線程對象顾复。

Thread定義了幾個構(gòu)造方法班挖,下面的這個是我們經(jīng)常使用的:

Thread(Runnable threadOb,String threadName);

這里,threadOb 是一個實現(xiàn)Runnable 接口的類的實例芯砸,并且 threadName指定新線程的名字萧芙。

新線程創(chuàng)建之后,你調(diào)用它的start()方法它才會運(yùn)行假丧。

void start();

實例

下面是一個創(chuàng)建線程并開始讓它執(zhí)行的實例:

// 創(chuàng)建一個新的線程

class NewThread implements Runnable {

Thread t;

NewThread() {

// 創(chuàng)建第二個新線程

t = new Thread(this, "Demo Thread");

System.out.println("Child thread: " + t);

t.start(); // 開始線程

}

// 第二個線程入口

public void run() {

try {

for(int i = 5; i > 0; i--) {

System.out.println("Child Thread: " + i);

// 暫停線程

Thread.sleep(50);

}

} catch (InterruptedException e) {

System.out.println("Child interrupted.");

}

System.out.println("Exiting child thread.");

}

}

public class ThreadDemo {

public static void main(String args[]) {

new NewThread(); // 創(chuàng)建一個新線程

try {

for(int i = 5; i > 0; i--) {

System.out.println("Main Thread: " + i);

Thread.sleep(100);

}

} catch (InterruptedException e) {

System.out.println("Main thread interrupted.");

}

System.out.println("Main thread exiting.");

}

}

編譯以上程序運(yùn)行結(jié)果如下:

Child thread: Thread[Demo Thread,5,main]

Main Thread: 5

Child Thread: 5

Child Thread: 4

Main Thread: 4

Child Thread: 3

Child Thread: 2

Main Thread: 3

Child Thread: 1

Exiting child thread.

Main Thread: 2

Main Thread: 1

Main thread exiting.

通過繼承Thread來創(chuàng)建線程

創(chuàng)建一個線程的第二種方法是創(chuàng)建一個新的類双揪,該類繼承Thread類,然后創(chuàng)建一個該類的實例包帚。

繼承類必須重寫run()方法渔期,該方法是新線程的入口點。它也必須調(diào)用start()方法才能執(zhí)行渴邦。該方法盡管被列為一種多線程實現(xiàn)方式疯趟,但是本質(zhì)上也是實現(xiàn)了 Runnable 接口的一個實例。

實例

// 通過繼承 Thread 創(chuàng)建線程

class NewThread extends Thread {

NewThread() {

// 創(chuàng)建第二個新線程

super("Demo Thread");

System.out.println("Child thread: " + this);

start(); // 開始線程

}

// 第二個線程入口

public void run() {

try {

for(int i = 5; i > 0; i--) {

System.out.println("Child Thread: " + i);

// 讓線程休眠一會

Thread.sleep(50);

}

} catch (InterruptedException e) {

System.out.println("Child interrupted.");

}

System.out.println("Exiting child thread.");

}

}

public class ExtendThread {

public static void main(String args[]) {

new NewThread(); // 創(chuàng)建一個新線程

try {

for(int i = 5; i > 0; i--) {

System.out.println("Main Thread: " + i);

Thread.sleep(100);

}

} catch (InterruptedException e) {

System.out.println("Main thread interrupted.");

}

System.out.println("Main thread exiting.");

}

}

編譯以上程序運(yùn)行結(jié)果如下:

Child thread: Thread[Demo Thread,5,main]

Main Thread: 5

Child Thread: 5

Child Thread: 4

Main Thread: 4

Child Thread: 3

Child Thread: 2

Main Thread: 3

Child Thread: 1

Exiting child thread.

Main Thread: 2

Main Thread: 1

Main thread exiting.

Thread 方法

下表列出了Thread類的一些重要方法:

序號方法描述1public void start()

使該線程開始執(zhí)行谋梭;Java?虛擬機(jī)調(diào)用該線程的 run 方法迅办。2public void run()

如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對象構(gòu)造的,則調(diào)用該 Runnable 對象的 run 方法章蚣;否則站欺,該方法不執(zhí)行任何操作并返回姨夹。3public final void setName(String name)

改變線程名稱,使之與參數(shù) name 相同矾策。4public final void setPriority(int priority)

更改線程的優(yōu)先級磷账。5public final void setDaemon(boolean on)

將該線程標(biāo)記為守護(hù)線程或用戶線程。6public final void join(long millisec)

等待該線程終止的時間最長為 millis 毫秒贾虽。7public void interrupt()

中斷線程逃糟。8public final boolean isAlive()

測試線程是否處于活動狀態(tài)。

測試線程是否處于活動狀態(tài)蓬豁。 上述方法是被Thread對象調(diào)用的绰咽。下面的方法是Thread類的靜態(tài)方法。

序號方法描述1public static void yield()

暫停當(dāng)前正在執(zhí)行的線程對象地粪,并執(zhí)行其他線程取募。2public static void sleep(long millisec)

在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計時器和調(diào)度程序精度和準(zhǔn)確性的影響蟆技。3public static boolean holdsLock(Object x)

當(dāng)且僅當(dāng)當(dāng)前線程在指定的對象上保持監(jiān)視器鎖時玩敏,才返回 true。4public static Thread currentThread()

返回對當(dāng)前正在執(zhí)行的線程對象的引用质礼。5public static void dumpStack()

將當(dāng)前線程的堆棧跟蹤打印至標(biāo)準(zhǔn)錯誤流旺聚。

實例

如下的ThreadClassDemo 程序演示了Thread類的一些方法:

// 文件名 : DisplayMessage.java

// 通過實現(xiàn) Runnable 接口創(chuàng)建線程

public class DisplayMessage implements Runnable

{

private String message;

public DisplayMessage(String message)

{

this.message = message;

}

public void run()

{

while(true)

{

System.out.println(message);

}

}

}

GuessANumber.java 文件代碼:

// 文件名 : GuessANumber.java

// 通過繼承 Thread 類創(chuàng)建線程

public class GuessANumber extends Thread

{

private int number;

public GuessANumber(int number)

{

this.number = number;

}

public void run()

{

int counter = 0;

int guess = 0;

do

{

guess = (int) (Math.random() * 100 + 1);

System.out.println(this.getName()

+ " guesses " + guess);

counter++;

}while(guess != number);

System.out.println("** Correct! " + this.getName()

+ " in " + counter + " guesses.**");

}

}

ThreadClassDemo.java 文件代碼:

// 文件名 : ThreadClassDemo.java

public class ThreadClassDemo

{

public static void main(String [] args)

{

Runnable hello = new DisplayMessage("Hello");

Thread thread1 = new Thread(hello);

thread1.setDaemon(true);

thread1.setName("hello");

System.out.println("Starting hello thread...");

thread1.start();

Runnable bye = new DisplayMessage("Goodbye");

Thread thread2 = new Thread(bye);

thread2.setPriority(Thread.MIN_PRIORITY);

thread2.setDaemon(true);

System.out.println("Starting goodbye thread...");

thread2.start();

System.out.println("Starting thread3...");

Thread thread3 = new GuessANumber(27);

thread3.start();

try

{

thread3.join();

}catch(InterruptedException e)

{

System.out.println("Thread interrupted.");

}

System.out.println("Starting thread4...");

Thread thread4 = new GuessANumber(75);

thread4.start();

System.out.println("main() is ending...");

}

}

運(yùn)行結(jié)果如下,每一次運(yùn)行的結(jié)果都不一樣眶蕉。

Starting hello thread...

Starting goodbye thread...

Hello

Hello

Hello

Hello

Hello

Hello

Hello

Hello

Hello

Starting thread3...

Hello

Hello

Starting thread4...

Hello

Hello

main() is ending...

通過 Callable 和 Future 創(chuàng)建線程

1. 創(chuàng)建 Callable 接口的實現(xiàn)類砰粹,并實現(xiàn) call() 方法,該 call() 方法將作為線程執(zhí)行體造挽,并且有返回值碱璃。

2. 創(chuàng)建 Callable 實現(xiàn)類的實例,使用 FutureTask 類來包裝 Callable 對象刽宪,該 FutureTask 對象封裝了該 Callable 對象的 call() 方法的返回值厘贼。

3. 使用 FutureTask 對象作為 Thread 對象的 target 創(chuàng)建并啟動新線程。

4. 調(diào)用 FutureTask 對象的 get() 方法來獲得子線程執(zhí)行結(jié)束后的返回值圣拄。

實例

public class CallableThreadTest implements Callable {

public static void main(String[] args)

{

CallableThreadTest ctt = new CallableThreadTest();

FutureTask ft = new FutureTask<>(ctt);

for(int i = 0;i < 100;i++)

{

System.out.println(Thread.currentThread().getName()+" 的循環(huán)變量i的值"+i);

if(i==20)

{

new Thread(ft,"有返回值的線程").start();

}

}

try

{

System.out.println("子線程的返回值:"+ft.get());

} catch (InterruptedException e)

{

e.printStackTrace();

} catch (ExecutionException e)

{

e.printStackTrace();

}

}

@Override

public Integer call() throws Exception

{

int i = 0;

for(;i<100;i++)

{

System.out.println(Thread.currentThread().getName()+" "+i);

}

return i;

}

}

創(chuàng)建線程的三種方式的對比

1. 采用實現(xiàn) Runnable嘴秸、Callable 接口的方式創(chuàng)建多線程時,線程類只是實現(xiàn)了 Runnable 接口或 Callable 接口庇谆,還可以繼承其他類岳掐。

2. 使用繼承 Thread 類的方式創(chuàng)建多線程時,編寫簡單饭耳,如果需要訪問當(dāng)前線程串述,則無需使用 Thread.currentThread() 方法,直接使用 this 即可獲得當(dāng)前線程寞肖。

線程的幾個主要概念

在多線程編程時纲酗,你需要了解以下幾個概念:

線程同步

線程間通信

線程死鎖

線程控制:掛起衰腌、停止和恢復(fù)

多線程的使用

有效利用多線程的關(guān)鍵是理解程序是并發(fā)執(zhí)行而不是串行執(zhí)行的。例如:程序中有兩個子系統(tǒng)需要并發(fā)執(zhí)行觅赊,這時候就需要利用多線程編程右蕊。

通過對多線程的使用,可以編寫出非常高效的程序吮螺。不過請注意饶囚,如果你創(chuàng)建太多的線程,程序執(zhí)行的效率實際上是降低了鸠补,而不是提升了萝风。

請記住,上下文的切換開銷也很重要紫岩,如果你創(chuàng)建了太多的線程规惰,CPU花費(fèi)在上下文的切換的時間將多于執(zhí)行程序的時間!

我自己是一個從事了6年的Java全棧工程師被因,最近整理了一套適合2019年學(xué)習(xí)的Java\大數(shù)據(jù)資料卿拴,從基礎(chǔ)的Java衫仑、大數(shù)據(jù)面向?qū)ο蟮竭M(jìn)階的框架知識

都有整理哦梨与,可以來我的主頁免費(fèi)領(lǐng)取哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末文狱,一起剝皮案震驚了整個濱河市粥鞋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞄崇,老刑警劉巖呻粹,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異苏研,居然都是意外死亡等浊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門摹蘑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筹燕,“玉大人,你說我怎么就攤上這事衅鹿∪鲎伲” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵大渤,是天一觀的道長制妄。 經(jīng)常有香客問我,道長泵三,這世上最難降的妖魔是什么耕捞? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任衔掸,我火速辦了婚禮,結(jié)果婚禮上俺抽,老公的妹妹穿的比我還像新娘具篇。我一直安慰自己,他們只是感情好凌埂,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布驱显。 她就那樣靜靜地躺著,像睡著了一般瞳抓。 火紅的嫁衣襯著肌膚如雪埃疫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天孩哑,我揣著相機(jī)與錄音栓霜,去河邊找鬼。 笑死横蜒,一個胖子當(dāng)著我的面吹牛胳蛮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丛晌,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼仅炊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了澎蛛?” 一聲冷哼從身側(cè)響起抚垄,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谋逻,沒想到半個月后呆馁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毁兆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年浙滤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片气堕。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡纺腊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出送巡,到底是詐尸還是另有隱情摹菠,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布骗爆,位于F島的核電站次氨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏摘投。R本人自食惡果不足惜煮寡,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一虹蓄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧幸撕,春花似錦薇组、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至貌矿,卻和暖如春炭菌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逛漫。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工黑低, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酌毡。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓克握,卻偏偏與公主長得像,于是被迫代替她去往敵國和親枷踏。 傳聞我的和親對象是個殘疾皇子菩暗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容