1.1 程序、進(jìn)程與線程
? 程序(program):為完成特定任務(wù),用某種語(yǔ)言編寫的一組指令的集合。即指一段
靜態(tài)的代碼,靜態(tài)對(duì)象。
? 進(jìn)程(process):程序的一次執(zhí)行過程,或是正在內(nèi)存中運(yùn)行的應(yīng)用程序。如:運(yùn)行
中的 QQ,運(yùn)行中的網(wǎng)易音樂播放器。
– 每個(gè)進(jìn)程都有一個(gè)獨(dú)立的內(nèi)存空間,系統(tǒng)運(yùn)行一個(gè)程序即是一個(gè)進(jìn)程從創(chuàng)
建、運(yùn)行到消亡的過程。(生命周期)
– 程序是靜態(tài)的,進(jìn)程是動(dòng)態(tài)的
– 進(jìn)程作為操作系統(tǒng)調(diào)度和分配資源的最小單位(亦是系統(tǒng)運(yùn)行程序的基
本單位),系統(tǒng)在運(yùn)行時(shí)會(huì)為每個(gè)進(jìn)程分配不同的內(nèi)存區(qū)域。
– 現(xiàn)代的操作系統(tǒng),大都是支持多進(jìn)程的,支持同時(shí)運(yùn)行多個(gè)程序。比如:
現(xiàn)在我們上課一邊使用編輯器,一邊使用錄屏軟件,同時(shí)還開著畫圖板,
dos 窗口等軟件。
? 線程(thread):進(jìn)程可進(jìn)一步細(xì)化為線程,是程序內(nèi)部的一條執(zhí)行路徑。
一 個(gè)進(jìn)程中至少有一個(gè)線程。
– 一個(gè)進(jìn)程同一時(shí)間若并行執(zhí)行多個(gè)線程,就是支持多線程的。
– 線程作為 CPU 調(diào)度和執(zhí)行的最小單位。
– 一個(gè)進(jìn)程中的多個(gè)線程共享相同的內(nèi)存單元,它們從同一個(gè)堆中分配對(duì)
象,可以訪問相同的變量和對(duì)象。這就使得線程間通信更簡(jiǎn)便、高效。但
多個(gè)線程操作共享的系統(tǒng)資源可能就會(huì)帶來安全的隱患。
– 下圖中,紅框的藍(lán)色區(qū)域?yàn)榫€程獨(dú)享,黃色區(qū)域?yàn)榫€程共享。
注意:
不同的進(jìn)程之間是不共享內(nèi)存的。
進(jìn)程之間的數(shù)據(jù)交換和通信的成本很高。
1.5.2 并行與并發(fā)
? 并行(parallel):指兩個(gè)或多個(gè)事件在同一時(shí)刻發(fā)生(同時(shí)發(fā)生)。
指在同一時(shí)刻,有多條指令在多個(gè) CPU 上同時(shí)執(zhí)行。比如:多個(gè)人同時(shí)做不同的事。
**? 并發(fā)(concurrency):指兩個(gè)或多個(gè)事件在同一個(gè)時(shí)間段內(nèi)發(fā)生。**即在一段時(shí)間內(nèi),有多條指令在單個(gè) CPU 上快速輪換、交替執(zhí)行,使得在宏觀上具有多個(gè)進(jìn)程同時(shí)執(zhí)行的效果。
2.創(chuàng)建和啟動(dòng)線程
2.1 概述
? Java 語(yǔ)言的 JVM 允許程序運(yùn)行多個(gè)線程,使用 java.lang.Thread 類代表線程,所
有的線程對(duì)象都必須是 Thread 類或其子類的實(shí)例。
? Thread 類的特性
– 每個(gè)線程都是通過某個(gè)特定 Thread 對(duì)象的 run()方法來完成操作的,因此
把 run()方法體稱為線程執(zhí)行體。
– 通過該 Thread 對(duì)象的 start()方法來啟動(dòng)這個(gè)線程,而非直接調(diào)用 run()
– 要想實(shí)現(xiàn)多線程,必須在主線程中創(chuàng)建新的線程對(duì)象。
2.2 方式 1:繼承 Thread 類
Java 通過繼承 Thread 類來創(chuàng)建并啟動(dòng)多線程的步驟如下:
- 定義 Thread 類的子類,并重寫該類的 run()方法,該 run()方法的方法體就代表了線程
需要完成的任務(wù) - 創(chuàng)建 Thread 子類的實(shí)例,即創(chuàng)建了線程對(duì)象
- 調(diào)用線程對(duì)象的 start()方法來啟動(dòng)該線程
代碼如下:
package com.atguigu.thread;
//自定義線程類
public class MyThread extends Thread {
//定義指定線程名稱的構(gòu)造方法
public MyThread(String name) {
//調(diào)用父類的 String 參數(shù)的構(gòu)造方法,指定線程的名稱
super(name);
}
/**
* 重寫 run 方法,完成該線程執(zhí)行的邏輯
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在執(zhí)行!"+i);
}
}
}
測(cè)試類:
package com.atguigu.thread;
public class TestMyThread {
public static void main(String[] args) {
//創(chuàng)建自定義線程對(duì)象 1
MyThread mt1 = new MyThread("子線程 1");
//開啟子線程 1
mt1.start();
//創(chuàng)建自定義線程對(duì)象 2
MyThread mt2 = new MyThread("子線程 2");
//開啟子線程 2
mt2.start();
//在主方法中執(zhí)行 for 循環(huán)
for (int i = 0; i < 10; i++) {
System.out.println("main 線程!"+i);
}
}
}
注意:
1.如果自己手動(dòng)調(diào)用 run()方法,那么就只是普通方法,沒有啟動(dòng)多線程模 式。
2.run()方法由 JVM 調(diào)用,什么時(shí)候調(diào)用,執(zhí)行的過程控制都有操作系統(tǒng)的 CPU 調(diào)度決定。
3.想要啟動(dòng)多線程,必須調(diào)用 start 方法。
4.一個(gè)線程對(duì)象只能調(diào)用一次 start()方法啟動(dòng),如果重復(fù)調(diào)用了,則將拋出 以上的異常“IllegalThreadStateException”。
2.3 方式 2:實(shí)現(xiàn) Runnable 接口
Java 有單繼承的限制,當(dāng)我們無法繼承 Thread 類時(shí),那么該如何做呢?在核心
類庫(kù)中提供了 Runnable 接口,我們可以實(shí)現(xiàn) Runnable 接口,重寫 run()方法,
然后再通過 Thread 類的對(duì)象代理啟動(dòng)和執(zhí)行我們的線程體 run()方法
步驟如下:
2. 定義 Runnable 接口的實(shí)現(xiàn)類,并重寫該接口的 run()方法,該 run()方法的方法體同樣是該線程的線程執(zhí)行體。
3. 創(chuàng)建 Runnable 實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為 Thread 的 target 參數(shù)來創(chuàng)建Thread 對(duì)象,該 Thread 對(duì)象才是真正 的線程對(duì)象。
4. 調(diào)用線程對(duì)象的 start()方法,啟動(dòng)線程。調(diào)用 Runnable 接口實(shí)現(xiàn)類的run 方法。
代碼如下:
package com.atguigu.thread;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " "
+ i);
}
}
}
測(cè)試類:
package com.atguigu.thread;
public class TestMyRunnable {
public static void main(String[] args) {
//創(chuàng)建自定義類對(duì)象 線程任務(wù)對(duì)象
MyRunnable mr = new MyRunnable();
//創(chuàng)建線程對(duì)象
Thread t = new Thread(mr, "長(zhǎng)江");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("黃河 " + i);
}
}
}
通過實(shí)現(xiàn) Runnable 接口,使得該類有了多線程類的特征。所有的分線程要執(zhí)行的代碼都在 run 方法里面。
在啟動(dòng)的多線程的時(shí)候,需要先通過 Thread 類的構(gòu)造方法 Thread(Runnable target) 構(gòu)造出對(duì)象,然后調(diào)用 Thread 對(duì)象的 start()方法來運(yùn)行多線程代碼。
實(shí)際上,所有的多線程代碼都是通過運(yùn)行 Thread 的 start()方法來運(yùn)行的。因此,不管是繼承 Thread 類還是實(shí)現(xiàn) Runnable 接口來實(shí)現(xiàn)多線程,最終還是通過 Thread 的對(duì)象的 API 來控制線程的,熟悉 Thread 類的 API 是進(jìn)行多線程編程的基礎(chǔ)。
說明:Runnable 對(duì)象僅僅作為 Thread 對(duì)象的 target,Runnable 實(shí)現(xiàn)類里包含的 run()方法僅作為線程執(zhí)行體。
而實(shí)際的線程對(duì)象依然是 Thread 實(shí)例,只是該 Thread 線程負(fù)責(zé)執(zhí)行其 target 的 run()方法。
2.5 對(duì)比兩種方式
聯(lián)系
Thread 類實(shí)際上也是實(shí)現(xiàn)了 Runnable 接口的類。即: public class Thread extends Object implements Runnable
區(qū)別
? 繼承 Thread:線程代碼存放 Thread 子類 run 方法中。
? 實(shí)現(xiàn) Runnable:線程代碼存在接口的子類的 run 方法。
實(shí)現(xiàn) Runnable 接口比繼承 Thread 類所具有的優(yōu)勢(shì)
? 避免了單繼承的局限性
? 多個(gè)線程可以共享同一個(gè)接口實(shí)現(xiàn)類的對(duì)象,非常適合多個(gè)相同線程來處理同一份資 源。
?增加程序的健壯性,實(shí)現(xiàn)解耦操作,代碼可以被多個(gè)線程共享,代碼和線程獨(dú)立。
3. Thread 類的常用結(jié)構(gòu)
3.1 構(gòu)造器
? public Thread() :分配一個(gè)新的線程對(duì)象。
? public Thread(String name) :分配一個(gè)指定名字的新的線程對(duì)象。
? public Thread(Runnable target) :指定創(chuàng)建線程的目標(biāo)對(duì)象,它實(shí)現(xiàn)了
Runnable 接口中的 run 方法
? public Thread(Runnable target,String name) :分配一個(gè)帶有指定目標(biāo)新的線程對(duì)象并指定名字。
3.2 常用方法系列 1
? public void run() :此線程要執(zhí)行的任務(wù)在此處定義代碼。
? public void start() :導(dǎo)致此線程開始執(zhí)行; Java 虛擬機(jī)調(diào)用此線程的 run 方法。
? public String getName() :獲取當(dāng)前線程名稱。
? public void setName(String name):設(shè)置該線程名稱。
? public static Thread currentThread() :返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用。在
Thread 子類中就是 this,通常用于主線程和 Runnable 實(shí)現(xiàn)類
? public static void sleep(long millis) :使當(dāng)前正在執(zhí)行的線程以指定的毫秒數(shù)暫停(暫時(shí)
停止執(zhí)行)。
? public static void yield():yield 只是讓當(dāng)前線程暫停一下,讓系統(tǒng)的線程調(diào)度器重新
調(diào)度一次,希望優(yōu)先級(jí)與當(dāng)前線程相同或更高的其他線程能夠獲得執(zhí)行機(jī)會(huì),但是這
個(gè)不能保證,完全有可能的情況是,當(dāng)某個(gè)線程調(diào)用了 yield 方法暫停之后,線程調(diào)
度器又將其調(diào)度出來重新執(zhí)行。
4.1 JDK1.5 之前:5 種狀態(tài)
線程的生命周期有五種狀態(tài):新建(New)、就緒(Runnable)、運(yùn)行
(Running)、阻塞(Blocked)、死亡(Dead)。 CPU 需要在多條線程之間切
換,于是線程狀態(tài)會(huì)多次在運(yùn)行、阻塞、就緒之間切換
1.新建
當(dāng)一個(gè) Thread 類或其子類的對(duì)象被聲明并創(chuàng)建時(shí),新生的線程對(duì)象處于新建狀
態(tài)。此時(shí)它和其他 Java 對(duì)象一樣,僅僅由 JVM 為其分配了內(nèi)存,并初始化了
實(shí)例變量的值。此時(shí)的線程對(duì)象并沒有任何線程的動(dòng)態(tài)特征,程序也不會(huì)執(zhí)行
它的線程體 run()。
2.就緒
但是當(dāng)線程對(duì)象調(diào)用了 start()方法之后,就不一樣了,線程就從新建狀態(tài)轉(zhuǎn)為
就緒狀態(tài)。JVM 會(huì)為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器,當(dāng)然,處于這個(gè)狀態(tài)中
的線程并沒有開始運(yùn)行,只是表示已具備了運(yùn)行的條件,隨時(shí)可以被調(diào)度。至
于什么時(shí)候被調(diào)度,取決于 JVM 里線程調(diào)度器的調(diào)度。
注意:
程序只能對(duì)新建狀態(tài)的線程調(diào)用 start(),并且只能調(diào)用一次,如果對(duì)
非新建狀態(tài)的線程,如已啟動(dòng)的線程或已死亡的線程調(diào)用 start()都會(huì)
報(bào)錯(cuò) IllegalThreadStateException 異常。
3.運(yùn)行
如果處于就緒狀態(tài)的線程獲得了 CPU 資源時(shí),開始執(zhí)行 run()方法的線程體代
碼,則該線程處于運(yùn)行狀態(tài)。如果計(jì)算機(jī)只有一個(gè) CPU 核心,在任何時(shí)刻只有
一個(gè)線程處于運(yùn)行狀態(tài),如果計(jì)算機(jī)有多個(gè)核心,將會(huì)有多個(gè)線程并行
(Parallel)執(zhí)行。
當(dāng)然,美好的時(shí)光總是短暫的,而且 CPU 講究雨露均沾。對(duì)于搶占式策略的系
統(tǒng)而言,系統(tǒng)會(huì)給每個(gè)可執(zhí)行的線程一個(gè)小時(shí)間段來處理任務(wù),當(dāng)該時(shí)間用
完,系統(tǒng)會(huì)剝奪該線程所占用的資源,讓其回到就緒狀態(tài)等待下一次被調(diào)度。
此時(shí)其他線程將獲得執(zhí)行機(jī)會(huì),而在選擇下一個(gè)線程時(shí),系統(tǒng)會(huì)適當(dāng)考慮線程
的優(yōu)先級(jí)。
4.阻塞
當(dāng)在運(yùn)行過程中的線程遇到如下情況時(shí),會(huì)讓出 CPU 并臨時(shí)中止自己的執(zhí)
行,進(jìn)入阻塞狀態(tài):
? 線程調(diào)用了 sleep()方法,主動(dòng)放棄所占用的 CPU 資源;
? 線程試圖獲取一個(gè)同步監(jiān)視器,但該同步監(jiān)視器正被其他線程持有;
? 線程執(zhí)行過程中,同步監(jiān)視器調(diào)用了 wait(),讓它等待某個(gè)通知(notify);
? 線程執(zhí)行過程中,同步監(jiān)視器調(diào)用了 wait(time)
? 線程執(zhí)行過程中,遇到了其他線程對(duì)象的加塞(join);
? 線程被調(diào)用 suspend 方法被掛起(已過時(shí),因?yàn)槿菀装l(fā)生死鎖);
當(dāng)前正在執(zhí)行的線程被阻塞后,其他線程就有機(jī)會(huì)執(zhí)行了。針對(duì)如上情況,當(dāng)
發(fā)生如下情況時(shí)會(huì)解除阻塞,讓該線程重新進(jìn)入就緒狀態(tài),等待線程調(diào)度器再
次調(diào)度它:
? 線程的 sleep()時(shí)間到;
? 線程成功獲得了同步監(jiān)視器;
? 線程等到了通知(notify);
? 線程 wait 的時(shí)間到了
? 加塞的線程結(jié)束了;
? 被掛起的線程又被調(diào)用了 resume 恢復(fù)方法(已過時(shí),因?yàn)槿菀装l(fā)生死鎖);
5.死亡
線程會(huì)以以下三種方式之一結(jié)束,結(jié)束后的線程就處于死亡狀態(tài):
? run()方法執(zhí)行完成,線程正常結(jié)束
? 線程執(zhí)行過程中拋出了一個(gè)未捕獲的異常(Exception)或錯(cuò)誤(Error)
? 直接調(diào)用該線程的 stop()來結(jié)束該線程(已過時(shí))
4.2 JDK1.5 及之后:6 種狀態(tài)
在 java.lang.Thread.State 的枚舉類中這樣定義:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
? NEW(新建):線程剛被創(chuàng)建,但是并未啟動(dòng)。還沒調(diào)用 start 方法。
? RUNNABLE(可運(yùn)行):這里沒有區(qū)分就緒和運(yùn)行狀態(tài)。因?yàn)閷?duì)于 Java 對(duì)象來說,只
能標(biāo)記為可運(yùn)行,至于什么時(shí)候運(yùn)行,不是 JVM 來控制的了,是 OS 來進(jìn)行調(diào)度的,
而且時(shí)間非常短暫,因此對(duì)于 Java 對(duì)象的狀態(tài)來說,無法區(qū)分。
? Teminated(被終止):表明此線程已經(jīng)結(jié)束生命周期,終止運(yùn)行。
? 重點(diǎn)說明,根據(jù) Thread.State 的定義,阻塞狀態(tài)分為三種:BLOCKED、WAITING、
TIMED_WAITING。
– BLOCKED(鎖阻塞):在 API 中的介紹為:一個(gè)正在阻塞、等待一個(gè)監(jiān)視
器鎖(鎖對(duì)象)的線程處于這一狀態(tài)。只有獲得鎖對(duì)象的線程才能有執(zhí)行
機(jī)會(huì)。
? 比如,線程 A 與線程 B 代碼中使用同一鎖,如果線程 A 獲取到
鎖,線程 A 進(jìn)入到 Runnable 狀態(tài),那么線程 B 就進(jìn)入到 Blocked
鎖阻塞狀態(tài)。
– TIMED_WAITING(計(jì)時(shí)等待):在 API 中的介紹為:一個(gè)正在限時(shí)等待
另一個(gè)線程執(zhí)行一個(gè)(喚醒)動(dòng)作的線程處于這一狀態(tài)。
? 當(dāng)前線程執(zhí)行過程中遇到 Thread 類的 sleep 或 join,Object 類
的 wait,LockSupport 類的 park 方法,并且在調(diào)用這些方法時(shí),
設(shè)置了時(shí)間,那么當(dāng)前線程會(huì)進(jìn)入 TIMED_WAITING,直到時(shí)間
到,或被中斷。
– WAITING(無限等待):在 API 中介紹為:一個(gè)正在無限期等待另一個(gè)線
程執(zhí)行一個(gè)特別的(喚醒)動(dòng)作的線程處于這一狀態(tài)。
? 當(dāng)前線程執(zhí)行過程中遇到遇到 Object 類的 wait,Thread 類的
join,LockSupport 類的 park 方法,并且在調(diào)用這些方法時(shí),沒
有指定時(shí)間,那么當(dāng)前線程會(huì)進(jìn)入 WAITING 狀態(tài),直到被喚醒。
– 通過 Object 類的 wait 進(jìn)入 WAITING 狀態(tài)的要有 Object 的
notify/notifyAll 喚醒;
– 通過 Condition 的 await 進(jìn)入 WAITING 狀態(tài)的要有
Condition 的 signal 方法喚醒;
– 通過 LockSupport 類的 park 方法進(jìn)入 WAITING 狀態(tài)的要有
LockSupport 類的 unpark 方法喚醒
– 通過 Thread 類的 join 進(jìn)入 WAITING 狀態(tài),只有調(diào)用 join
方法的線程對(duì)象結(jié)束才能讓當(dāng)前線程恢復(fù);
說明:當(dāng)從 WAITING 或 TIMED_WAITING 恢復(fù)到 Runnable 狀態(tài)時(shí),如果發(fā)現(xiàn)
當(dāng)前線程沒有得到監(jiān)視器鎖,那么會(huì)立刻轉(zhuǎn)入 BLOCKED 狀態(tài)。文章來源:http://www.zghlxwxcb.cn/news/detail-474507.html
OR文章來源地址http://www.zghlxwxcb.cn/news/detail-474507.html
到了這里,關(guān)于Java 高級(jí)應(yīng)用-多線程-(一)實(shí)現(xiàn) Runnable 接口與繼承 Thread 類的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!