目的
為什么在Java中要使用多線程
Java 給多線程編程提供了內(nèi)置的支持。 一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。
多線程是多任務(wù)的一種特別的形式,但多線程使用了更小的資源開銷。
這里定義和線程相關(guān)的另一個(gè)術(shù)語 - 進(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é)束。
多線程能滿足程序員編寫高效率的程序來達(dá)到充分利用 CPU 的目的。
進(jìn)程和線程的區(qū)別
進(jìn)程:每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文),進(jìn)程間的切換會(huì)有較大的開銷,一個(gè)進(jìn)程包含1--n個(gè)線程。(進(jìn)程是資源分配的最小單位)
線程:同一類線程共享代碼和數(shù)據(jù)空間,每個(gè)線程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC),線程切換開銷小。(線程是cpu調(diào)度的最小單位)。
多進(jìn)程是指操作系統(tǒng)能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)。
多線程是指在同一程序中有多個(gè)順序流在執(zhí)行。
如何創(chuàng)建一個(gè)線程
我們?nèi)绾卧谝粋€(gè)程序里面創(chuàng)建一個(gè)多線程呢?
我們學(xué)習(xí)了兩種方法來創(chuàng)建一個(gè)線程
一、繼承Thread類創(chuàng)建線程類
下面就是如何使用這個(gè)方法的步驟:
? 定義一個(gè)繼承Thread類的子類,并重寫該類的run()方法;
? 創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建了線程對象;
? 調(diào)用該線程對象的start()方法啟動(dòng)線程。
代碼示例:
class ThreadDemo extends Thread {
@Override
public void run(){
String name=Thread.currentThread().getName();
for(int i=0;i<100;i++){
System.out.println(name+":"+(i+1));
if(this!=MyClass.tt2){
try{
MyClass.tt2.join();
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}
super.run();
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.setName("子線程1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.setName("子線程2");
T2.start();
}
}
二、實(shí)現(xiàn)Runnable接口創(chuàng)建線程類
通過實(shí)現(xiàn)Runnable接口創(chuàng)建線程類的具體步驟
? 定義Runnable接口的實(shí)現(xiàn)類,并重寫該接口的run()方法;
? 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為Thread的target對象,即該Thread對象才是真正的線程對象。
class Demo implements Runnable {
@Override
public void run(){
String name=Thread.currentThread().getName();
for(int i=0;i<100;i++){
System.out.println(name+":"+(i+1));
if(this!=MyClass.tt2){
try{
MyClass.tt2.join();
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}
super.run();
}
}
public class TestThread {
public static void main(String args[]) {
Demo R1 = new Demo();
Thread onTread2 = new Thread(Demo);
onTread1.setName("子線程1");
onTread1.start();
Demo R2 = new Demo();
Thread onTread2= new Thread(Demo);
onTread2.setName("子線程2");
onTread2.start();
}
}
一個(gè)線程的生命周期
線程是一個(gè)動(dòng)態(tài)執(zhí)行的過程,它也有一個(gè)從產(chǎn)生到死亡的過程。
線程和進(jìn)程一樣分為五個(gè)階段:創(chuàng)建、就緒、運(yùn)行、阻塞、終止。
下圖顯示了一個(gè)線程完整的生命周期。

新建狀態(tài):
使用 new 關(guān)鍵字和 Thread 類或其子類建立一個(gè)線程對象后,該線程對象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序 start() 這個(gè)線程。
就緒狀態(tài):
當(dāng)線程對象調(diào)用了start()方法之后,該線程就進(jìn)入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊(duì)列中,要等待JVM里線程調(diào)度器的調(diào)度。
運(yùn)行狀態(tài):
處于運(yùn)行狀態(tài)的線程最為復(fù)雜,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。
1、如果就緒狀態(tài)的線程獲取 CPU 資源,就可以執(zhí)行 run(),此時(shí)線程便處于運(yùn)行狀態(tài)。
2、如果該線程失去了cpu資源,就會(huì)又從運(yùn)行狀態(tài)變?yōu)榫途w狀態(tài)。重新等待系統(tǒng)分配資源。也可以對在運(yùn)行狀態(tài)的線程調(diào)用yield()方法,它就會(huì)讓出cpu資源,再次變?yōu)榫途w狀態(tài)。
注: 當(dāng)發(fā)生如下情況是,線程會(huì)從運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài):
①、線程調(diào)用sleep方法主動(dòng)放棄所占用的系統(tǒng)資源
②、線程調(diào)用一個(gè)阻塞式IO方法,在該方法返回之前,該線程被阻塞
③、線程試圖獲得一個(gè)同步監(jiān)視器,但更改同步監(jiān)視器正被其他線程所持有
④、線程在等待某個(gè)通知(notify)
⑤、程序調(diào)用了線程的suspend方法將線程掛起。不過該方法容易導(dǎo)致死鎖,所以程序應(yīng)該盡量避免使用該方法。
當(dāng)線程的run()方法執(zhí)行完,或者被強(qiáng)制性地終止,例如出現(xiàn)異常,或者調(diào)用了stop()、desyory()方法等等,就會(huì)從運(yùn)行狀態(tài)轉(zhuǎn)變?yōu)樗劳鰻顟B(tài)。
阻塞狀態(tài):
如果一個(gè)線程執(zhí)行了sleep(睡眠)、suspend(掛起)等方法,失去所占用資源之后,該線程就從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)。在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)??梢苑譃槿N:
等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait() 方法,使線程進(jìn)入到等待阻塞狀態(tài)。
同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)橥芥i被其他線程占用)。
其他阻塞:通過調(diào)用線程的 sleep() 或 join() 發(fā)出了 I/O 請求時(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)。
Thread類的一些重要方法:
| 序號 | 方法描述 |
|---|---|
| 1 | public void start() 使該線程開始執(zhí)行;Java 虛擬機(jī)調(diào)用該線程的 run 方法。 |
| 2 | public void run() 如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對象構(gòu)造的,則調(diào)用該 Runnable 對象的 run 方法;否則,該方法不執(zhí)行任何操作并返回。 |
| 3 | public final void setName(String name) 改變線程名稱,使之與參數(shù) name 相同。 |
| 4 | public final void setPriority(int priority) 更改線程的優(yōu)先級。 |
| 5 | public final void setDaemon(boolean on) 將該線程標(biāo)記為守護(hù)線程或用戶線程。 |
| 6 | public final void join(long millisec) 等待該線程終止的時(shí)間最長為 millis 毫秒。 |
| 7 | public void interrupt() 中斷線程。 |
| 8 | public final boolean isAlive() 測試線程是否處于活動(dòng)狀態(tài)。 |
測試線程是否處于活動(dòng)狀態(tài)。 上述方法是被Thread對象調(diào)用的。下面的方法是Thread類的靜態(tài)方法。
| 序號 | 方法描述 |
|---|---|
| 1 | public static void yield() 暫停當(dāng)前正在執(zhí)行的線程對象,并執(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)前線程在指定的對象上保持監(jiān)視器鎖時(shí),才返回 true。 |
| 4 | public static Thread currentThread() 返回對當(dāng)前正在執(zhí)行的線程對象的引用。 |
| 5 | public static void dumpStack() 將當(dāng)前線程的堆棧跟蹤打印至標(biāo)準(zhǔn)錯(cuò)誤流。 |