32-多執行緒--概述+Thread類+多執行緒的建立方式(繼承Thread類+實現Runnable介面)+Runnable介面+執行緒的名稱+執行緒的狀態
一、概述
1、程序:對應的是一個應用程式在記憶體中的所屬空間。程序是不直接執行的,它只是在分配該應用程式的記憶體空間
注:如果一個程式在記憶體中開闢了空間,就代表它在執行。不執行要釋放空間
2、執行緒:程序中的一個負責程式執行的控制單元,也叫執行路徑。一個程序中可以有多個執行路徑,稱之為多執行緒。一個程序中至少要有一個執行緒(執行緒是負責執行的)
3、開啟多個執行緒是為了同時執行多部分程式碼。每一個執行緒都有自己執行的內容,這個內容稱為執行緒要執行的任務
4、多執行緒的好處與弊端
(1)好處:解決了多部分同時執行的問題
(2)弊端:執行緒太多會導致效率的降低
注:某一時刻,只有一個程式的一個執行路徑在執行
5、應用程式的執行,都是CUP在做著快速的切換完成的。這個切換是隨機的,依賴於時間片(CPU負責記憶體中程式的執行,做快速隨機的切換動作)
二、java.lang.Thread
1、public class Thread extends Object implements Runnable:執行緒是程式中的執行執行緒。Java虛擬機器允許應用程式併發地執行多個執行執行緒
2、每個執行緒都有一個標識名,多個執行緒可以同名。如果執行緒建立時沒有指定標識名,就會為其生成一個新名稱
3、欄位
優先順序:指能獲取到CPU執行權的機率。優先順序越大,獲取的機率越高。範圍:1--10
(1)static int MAX_PRIORITY:執行緒可以具有的最高優先順序(10)
(2)static int MIN_PRIORITY:執行緒可以具有的最低優先順序(1)
(3)static int NORM_PRIORITY:分配給執行緒的預設優先順序(5)
4、建構函式
(1)Thread():分配新的Thread物件。這種構造方法與Thread(null, null, gname)具有相同的作用,其中gname是一個新生成的名稱。自動生成的名稱的形式為“Thread-”+n,其中的n為整數
(2)Thread(Runnable target)
(3)Thread(Runnable target, String name)
(4)Thread(String name)
(5)Thread(ThreadGroup group, Runnable target)
(6)Thread(ThreadGroup group, Runnable target, String name):分配新的Thread物件,以便將target作為其執行物件,將指定的name作為其名稱,並作為group所引用的執行緒組的一員
(7)Thread(ThreadGroup group, Runnable target, String name, long stackSize):分配新的Thread物件,以便將target作為其執行物件,將指定的name作為其名稱,作為group所引用的執行緒組的一員,並具有指定的堆疊大小
(8)Thread(ThreadGroup group, String name)
5、方法
(1)static Thread currentThread():返回對當前正在執行的執行緒物件的引用
(2)void start():使該執行緒開始執行。Java虛擬機器呼叫該執行緒的run()方法。結果是兩個執行緒併發地執行:當前執行緒(從呼叫返回給start()方法)和另一個執行緒(執行其run()方法)
注:多次啟動一個執行緒是非法的。特別是當執行緒已經結束執行後,不能再重新啟動
(3)void run():如果該執行緒是使用獨立的Runnable執行物件構造的,則呼叫該Runnable物件的run()方法。否則,該方法不執行任何操作並返回。Thread的子類應該重寫該方法
(4)void interrupt():中斷執行緒
(5)static boolean interrupted():測試當前執行緒是否已經中斷。執行緒的中斷狀態由該方法清除。換句話說,如果連續兩次呼叫該方法,則第二次呼叫將返回false。執行緒中斷被忽略,因為在中斷時不處於活動狀態的執行緒將由此返回false的方法反映出來
(6)boolean isInterrupted():測試執行緒是否已經中斷。執行緒的中斷狀態不受該方法的影響。執行緒中斷被忽略,因為在中斷時不處於活動狀態的執行緒將由此返回false的方法反映出來
(7)static void sleep(long millis) throws InterruptedException:在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。該執行緒不丟失任何監視器的所屬權
(8)static void sleep(long millis, int nanos) throws InterruptedException:在指定的毫秒數加指定的納秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。該執行緒不丟失任何監視器的所屬權
(9)static void yield():暫停當前正在執行的執行緒物件,並執行其他執行緒
注:執行到Thread.yield(),釋放執行權,讓其他執行緒和該執行緒一樣有機會再次獲取執行權
(10)final void join() throws InterruptedException:等待該執行緒終止
(11)final void join(long millis) throws InterruptedException:等待該執行緒終止的時間最長為millis毫秒。超時為0意味著要一直等下去
(12)final void join(long millis, int nanos) throws InterruptedException:等待該執行緒終止的時間最長為millis毫秒+nanos納秒
(13)final void checkAccess():判定當前執行的執行緒是否有權修改該執行緒。如果有安全管理器,則呼叫其checkAccess()方法,並將該執行緒作為其引數。這可能導致丟擲SecurityException
(14)long getId():返回該執行緒的識別符號。執行緒ID是一個正的long數,在建立該執行緒時生成。執行緒ID是唯一的,並終生不變。執行緒終止時,該執行緒ID可以被重新使用
(15)final String getName():返回該執行緒的名稱
(16)final void setName(String name):改變執行緒名稱,使之與引數name相同。首先呼叫執行緒的checkAccess()方法,且不帶任何引數。這可能丟擲SecurityException
(17)final int getPriority():返回執行緒的優先順序
(18)final void setPriority(int newPriority):更改執行緒的優先順序。首先呼叫執行緒的checkAccess()方法,且不帶任何引數。這可能丟擲SecurityException。在其他情況下,執行緒優先順序被設定為指定的newPriority和該執行緒的執行緒組的最大允許優先順序相比較小的一個
//將執行緒t的優先順序設定為最大優先順序
t.setPriority(Thread.MAX_PRIORITY);
(19)final boolean isDaemon():測試該執行緒是否為守護執行緒
(20)final void setDaemon(boolean on):將該執行緒標記為守護執行緒或使用者執行緒。當正在執行的執行緒都是守護執行緒時,Java虛擬機器退出。該方法必須在啟動執行緒前呼叫。該方法首先呼叫該執行緒的checkAccess()方法,且不帶任何引數。這可能丟擲SecurityException(在當前執行緒中)
(21)final boolean isAlive():測試執行緒是否處於活動狀態。如果執行緒已經啟動且尚未終止,則為活動狀態
(22)static int activeCount():返回當前執行緒的執行緒組中活動執行緒的數目
(23)Thread.State getState():返回該執行緒的狀態。該方法用於監視系統狀態,不用於同步控制
(24)final ThreadGroup getThreadGroup():返回該執行緒所屬的執行緒組。如果該執行緒已經終止(停止執行),該方法則返回null
(25)StackTraceElement[] getStackTrace():返回一個表示該執行緒堆疊轉儲的堆疊跟蹤元素陣列。如果該執行緒尚未啟動或已經終止,則該方法將返回一個零長度陣列。如果返回的陣列不是零長度的,則其第一個元素代表堆疊頂,它是該序列中最新的方法呼叫。最後一個元素代表堆疊底,是該序列中最舊的方法呼叫
(26)static Map<Thread, StackTraceElement[]> getAllStackTraces():返回所有活動執行緒的堆疊跟蹤的一個對映。對映鍵是執行緒,而每個對映值都是一個StackTraceElement陣列,該陣列表示相應Thread的堆疊轉儲。返回的堆疊跟蹤的格式都是針對getStackTrace()方法指定的
注:在呼叫該方法的同時,執行緒可能也在執行。每個執行緒的堆疊跟蹤僅代表一個快照,並且每個堆疊跟蹤都可以在不同時間獲得。如果虛擬機器沒有執行緒的堆疊跟蹤資訊,則對映值中將返回一個零長度陣列。如果有安全管理器,則通過RuntimePermission("getStackTrace")許可權和RuntimePermission("modifyThreadGroup")許可權呼叫其checkPermission()方法,檢視是否可以獲取所有執行緒的堆疊跟蹤
(27)static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler():返回執行緒由於未捕獲到異常而突然終止時呼叫的預設處理程式。如果返回值為null,則沒有預設處理程式
(28)static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):設定當執行緒由於未捕獲到異常而突然終止,並且沒有為該執行緒定義其他處理程式時所呼叫的預設處理程式。未捕獲到的異常處理首先由執行緒控制,然後由執行緒的ThreadGroup物件控制,最後由未捕獲到的預設異常處理程式控制。如果執行緒不設定明確的未捕獲到的異常處理程式,並且該執行緒的執行緒組(包括父執行緒組)未特別指定其uncaughtException方法,則將呼叫預設處理程式的uncaughtException方法。通過設定未捕獲到的預設異常處理程式,應用程式可以為那些已經接受系統提供的任何“預設”行為的執行緒改變未捕獲到的異常處理方式(如記錄到某一特定裝置或檔案)
注:未捕獲到的預設異常處理程式通常不應順從該執行緒的ThreadGroup物件,因為這可能導致無限遞迴
(29)Thread.UncaughtExceptionHandler getUncaughtExceptionHandler():返回該執行緒由於未捕獲到異常而突然終止時呼叫的處理程式。如果該執行緒尚未明確設定未捕獲到的異常處理程式,則返回該執行緒的ThreadGroup物件,除非該執行緒已經終止,在這種情況下,將返回null
(30)void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):設定該執行緒由於未捕獲到異常而突然終止時呼叫的處理程式。通過明確設定未捕獲到的異常處理程式,執行緒可以完全控制它對未捕獲到的異常作出響應的方式。如果沒有設定這樣的處理程式,則該執行緒的ThreadGroup物件將充當其處理程式
(31)ClassLoader getContextClassLoader():返回該執行緒的上下文ClassLoader。上下文ClassLoader由執行緒建立者提供,供運行於該執行緒中的程式碼在載入類和資源時使用。如果未設定,則預設為父執行緒的ClassLoader上下文。原始執行緒的上下文ClassLoader通常設定為用於載入應用程式的類載入器
(32)void setContextClassLoader(ClassLoader cl):設定該執行緒的上下文ClassLoader。上下文ClassLoader可以在建立執行緒設定,並允許建立者在載入類和資源時向該執行緒中執行的程式碼提供適當的類載入器。如果有安全管理器,則通過RuntimePermission("setContextClassLoader")許可權呼叫其checkPermission()方法,檢視是否可以設定上下文ClassLoader
(33)static void dumpStack():將當前執行緒的堆疊跟蹤列印至標準錯誤流。該方法僅用於除錯
(34)static boolean holdsLock(Object obj):當且僅當當前執行緒在指定的物件上保持監視器鎖時,才返回true。該方法旨在使程式能夠斷言當前執行緒已經保持一個指定的鎖:assert Thread.holdsLock(obj);
(35)static int enumerate(Thread[] tarray):將當前執行緒的執行緒組及其子組中的每一個活動執行緒複製到指定的陣列中。該方法只調用當前執行緒的執行緒組的enumerate方法,且帶有陣列引數
(36)String toString():返回該執行緒的字串表示形式,包括執行緒名稱、優先順序和執行緒組(意味著執行緒物件本身就具備著對應的字串表現形式,而該表現形式來自於Object的覆蓋)
三、多執行緒的建立方式之一--繼承Thread類
1、通過繼承Thread類建立多執行緒的步驟:
(1)定義一個類繼承Thread類
(2)覆蓋Thread類中的run()方法,將執行緒要執行的自定義的任務程式碼封裝到run()方法中
(3)建立Thread類的子類物件,建立執行緒
(4)呼叫start()方法開啟執行緒,並呼叫執行緒的任務run()方法執行
注:如果沒有呼叫start()方法開啟執行緒,而是直接呼叫run()方法,就只有一個執行緒(主執行緒)。此時會按順序執行程式碼
class ZiThread extends Thread {
/**
* 繼承Thread類,覆蓋其run()方法,自定義新執行緒需要執行的任務程式碼
*/
@Override
public void run() {
//自定義任務程式碼
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "......" + i);
}
}
}
public class Test {
//主執行緒
public static void main(String[] args) {
//建立Thread的子類物件
ZiThread zt1 = new ZiThread();
ZiThread zt2 = new ZiThread();
//開啟執行緒,JVM自動呼叫run()方法執行
zt1.start();
zt2.start();
}
}
2、start()方法被呼叫後,會去做兩件事情:
(1)開啟執行緒
(2)呼叫run()方法
即 使該執行緒開始執行;Java虛擬機器呼叫該執行緒的run()方法
3、開啟多執行緒後,控制檯輸出的順序未改變,可調大範圍再次嘗試。因為CPU的執行速度很快,可能還沒來得及切換執行緒就已經執行完了。如果調大範圍後,控制檯輸出的順序依舊未改變,此時需要考慮程式是否有問題,有幾個執行緒在執行,執行緒是否開啟等
四、java.lang.Runnable
1、interface Runnable:Runnable介面應該由那些打算通過某一執行緒執行其例項的類來實現。類必須定義一個稱為run的無引數方法
2、Runnable為非Thread子類的類提供了一種啟用方式。通過例項化某個Thread例項並將自身作為執行目標,就可以執行實現Runnable的類而無需建立Thread的子類。大多數情況下,如果只想重寫run()方法,而不重寫其他Thread方法,那麼應使用Runnable介面。除非打算修改或增強類的基本行為,否則不應為該類建立子類
注:
(1)Thread類也實現了Runnable介面,並對其run()方法進行了預設實現
(2)如果不是執行緒體系,就不要繼承Thread類。需要擴充套件的功能,首選使用介面
(3)Runnable的出現,本身就是將執行緒任務進行物件的封裝(將任務封裝成物件,這是一個思想的變化)
3、方法
(1)void run():使用實現介面Runnable的物件建立一個執行緒時,啟動該執行緒將導致在獨立執行的執行緒中呼叫物件的run()方法。方法run()的常規協定是,它可能執行任何所需的動作
注:Runnable介面只有一個方法run(),用來封裝執行緒任務
五、多執行緒的建立方式之二--實現Runnable介面
1、通過實現Runnable介面建立多執行緒的步驟:
(1)定義一個類實現Runnable介面
(2)覆蓋介面中的run()方法,將執行緒要執行的自定義的任務程式碼封裝到run()方法中
(3)通過Thread類建立執行緒物件,並將Runnable介面的子類物件作為Thread類的建構函式的引數進行傳遞
(4)呼叫執行緒物件的start()方法開啟執行緒
注:一個物件如果沒有繼承Thread,它就不是執行緒物件,就不能呼叫start()開啟執行緒
class ZiRunnable implements Runnable {
/**
* 實現Runnable介面,覆蓋其run()方法,自定義新執行緒需要執行的任務程式碼
*/
@Override
public void run() {
//自定義任務程式碼
show();
}
/**
* ZiRunnable中原有的方法
* 因為要使用多執行緒,所以在run()中呼叫show()方法,而不需要將show()中的程式碼複製到run()中
*/
public void show() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "......" + i);
}
}
}
public class Test {
//主執行緒
public static void main(String[] args) {
ZiRunnable zr = new ZiRunnable();
//建立Thread物件,並將Runnable介面的子類物件作為引數傳遞
Thread t1 = new Thread(zr);
Thread t2 = new Thread(zr);
//開啟執行緒,JVM自動呼叫run()方法執行
t1.start();
t2.start();
}
}
2、為什麼把Runnable介面的子類物件傳給Thread類的建構函式?
因為執行緒的任務封裝在Runnable介面的子類物件的run()方法中,而執行緒物件在建立時就必須明確要執行的任務(如果不給Thread的建構函式傳參,執行緒要執行的任務是Thread類的run()方法中定義的內容)
3、Thread類的簡化原始碼及各情況分析
class Thread {
private Runnable r;
Thread() {
}
Thread(Runnable r) {
this.r = r;
}
public void run() {
if (r != null) {
r.run();
}
}
public void start() {
run();
}
}
/**
* 情況一:通過繼承Thread類建立多執行緒
*/
class ZiThread extends Thread {
@Override
public void run() {
//......
}
}
/**
分析:
public static void main(String[] args) {
ZiThread zt = new ZiThread();
zt.start();
}
zt.start():子類物件呼叫start(),但子類本身沒有start(),所以執行父類Thread中的start()方法
接著在start()方法體中呼叫run()方法,根據動態繫結,呼叫子類中的run()方法,與父類Thread中的run()無關
實際執行的是:子類中覆蓋父類Thread的run()方法
*/
/**
* 情況二:通過實現Runnable介面建立多執行緒
*/
class ZiRunnable implements Runnable {
@Override
public void run() {
//......
}
}
/**
分析:
public static void main(String[] args) {
Thread t = new Thread();
t.start();
}
new Thread():建立Thread物件,呼叫空參建構函式。Runnable r未被賦值,為null
t.start():呼叫Thread的start()方法,接著在start()方法體中呼叫run()方法
執行Thread的run()方法,r為null,執行run()的方法體什麼都沒有做
public static void main(String[] args) {
ZiRunnable zr = new ZiRunnable();
Thread t = new Thread(zr);
t.start();
}
new Thread(zr):建立Thread物件,呼叫帶Runnable引數的建構函式,傳入zr。Runnable r被賦值為zr
t.start():呼叫Thread的start()方法,接著在start()方法體中呼叫run()方法
執行Thread的run()方法,r為zr,在run()方法體中呼叫zr的run()
實際執行的是:Runnable子類物件中的run()方法
*/
4、通過實現Runnable介面建立多執行緒的好處
(1)將執行緒的任務從執行緒的子類中分離出來,進行了單獨的封裝。按照面向物件的思想,將任務封裝成物件
(2)避免了java單繼承的侷限性
實際開發中,通過實現Runnable介面建立多執行緒較為常用
六、執行緒的名稱
1、在建立執行緒子類物件時,該執行緒子類物件就完成了名稱的定義。格式為:Thread-編號,編號從0開始(執行緒一建立就帶著編號)
2、final String getName():獲取執行緒的名稱
3、static Thread currentThread():返回對當前正在執行的執行緒物件的引用
//獲取當前執行執行緒物件
Thread t = Thread.currentThread();
//獲取當前執行執行緒的名稱
Thread.currentThread().getName();
4、Thread(String name):Thread類的建構函式之一,為新建立的執行緒指定名稱name
class ZiThread extends Thread {
private String name;
ZiThread() {
}
ZiThread(String name) {
//顯示呼叫父類Thread的建構函式Thread(String name),為新建立的執行緒指定名稱name
//如果此處不顯示呼叫super(name),預設為super(),則新建立的執行緒名稱為預設的 Thread+編號
// super(name);
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i + "......" + Thread.currentThread().getName());
}
}
}
public class Test {
//主執行緒的名字就是main
public static void main(String[] args) {
//傳入為新建立執行緒指定的名稱
// 一定要執行父類Thread的Thread(String name)建構函式才有效,否則還是預設名稱
ZiThread zt1 = new ZiThread("zhangsan");
ZiThread zt2 = new ZiThread("xiaoqiang");
zt1.start();
zt2.start();
System.out.println("over......" + Thread.currentThread().getName());
}
}
七、執行緒的狀態
1、主函式結束了,JVM不一定結束。只要還有正在執行的執行緒,JVM就會存在
2、每個執行緒在棧記憶體中都有自己獨立的空間,每個run()方法都有自己所屬的執行緒的棧區。一個執行緒出現異常,該執行緒掛掉,但不影響其他執行緒的執行
3、執行緒的常見狀態:
(1)被建立:建立不代表執行,必須有start()開啟才有資格執行
(2)執行:既具備著CPU的執行資格,又具備著CPU的執行權
(3)臨時阻塞(等待):具備著執行資格,但不具備執行權,正在等待執行權
(4)凍結:在釋放執行權的同時釋放執行資格。特點:執行緒不再執行,但是還存活
(5)消亡
注:
(1)執行資格:可以被CPU處理,在處理佇列中排隊(會因CPU切換到而執行)
(2)執行權:正在被CPU處理
4、執行緒一開啟,不管開啟多少個,其中只有一個是具備執行權的。而且執行權在不斷做切換,切到誰,誰就具有執行權,沒被切到的都是臨時阻塞狀態
5、執行緒的狀態轉換圖
說明:
(1)sleep(time):執行緒的睡眠方法,必須指定時間。時間一到,自動醒來
(2)wait():沒有引數的情況下,執行緒醒不過來
(3)notify():喚醒。如果執行緒被wait(),可用此方式將執行緒喚醒
6、狀態轉換說明:
(1)執行和阻塞兩種狀態不斷切換
(2)凍結狀態時間到,醒來不一定立即執行
(3)想凍結必須具備執行資格才能去。只有獲取CPU執行權執行,才有資格呼叫sleep(time),才會凍結
八、總結
執行緒開啟必須要有任務
(1)要麼繼承Thread,覆蓋Thread的run()方法
(2)要麼實現Runnable介面,把任務封裝成物件傳入