本份隨記主要為狂神老師的Java多執行緒教學的學習筆記,記載了視訊中一些有關基礎概念以及部分程式碼示例。隨記分為1-3共三份,知識點記錄的不是很深入,以後的學習過程中隨時補充。
1 有關基礎概念
1.1 核心概念
- 執行緒就是獨立的執行路徑
- 程式執行時,即使沒有自己建立執行緒,後臺也會由多個執行緒(主執行緒、gc執行緒)
- main()稱之為主執行緒,為系統的入口,用於執行整個程式
- 一個程式中,若開闢多個執行緒,執行緒的執行由排程器安排排程,排程器與作業系統緊密相關,先後順序不能被人為干預。
- 對同一份資源操作時,會存在資源搶奪問題,需加入併發控制
- 執行緒會帶來額外的開銷(cpu排程時間,併發控制開銷)
- 每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料不一致(佇列和鎖)。
1.2 程式、程式、執行緒
- 程式(靜態概念):程式是指令和資料的有序集合,本身沒有任何執行的含義
- 程式(動態概念):程式的一次執行過程,是系統資源分配的單位
- 執行緒:CPU排程和分派的基本單位。(程式中可包含至少一個的若干執行緒)
- 程式 vs 執行緒:
同一程式中的執行緒使用相同的地址空間,而不同的程式則不會。這允許執行緒讀寫公共共享和資料結構和變數,也增加了執行緒之間的通訊。然而,程式間通訊(即IPC)是非常困難的,並且需要耗費大量資源。
1.3 併發、並行
- 並行:兩個或多個事務在同一時刻發生。
實際多執行緒為並行執行:多個cpu(多核)共同執行執行緒。 - 併發:兩個或多個事務在同一時間間隔內發生。
模擬多執行緒為併發執行:在一個cpu情況下同一時間點只能執行一個執行緒,但切換很快。
2 執行緒建立
2.1 Thread class(繼承Thread類)
- 自定義執行緒類繼承Thread類
- 子類重寫父類的run方法,編寫程式執行體
- 分配並啟動子類的例項(建立執行緒物件,呼叫start方法啟動執行緒)
程式示例:
點選檢視程式碼
// 1.建立子類繼承自Thread類
public class CreateThread extends Thread{
//2.重寫run方法
@Override
public void run() {
for(int i = 0; i < 5; i++){
System.out.println("Run.No." + i);
}
}
public static void main(String[] args){
//3.建立一個執行緒物件
CreateThread createThread1 = new CreateThread();
//3.呼叫start方法開啟執行緒
createThread1.start();
for (int i = 0; i < 5; i++) {
System.out.println("No." + i);
}
}
}
注: 執行緒開啟不一定立即執行,由CPU排程執行,執行緒執行順序不是由定義順序決定的。
2.2 Runnable介面的實現
- 定義MyRunnable類實現Runnable介面
- 實現run方法,編寫程式執行體
- 建立執行緒物件(代理),呼叫start方法啟動執行緒。
new Thread(implementRunnable1).start();
程式示例:
點選檢視程式碼
// 1.實現runnable介面
public class ImplementRunnable implements Runnable{
//2.重寫run方法
@Override
public void run() {
for(int i = 0; i < 200; i++){
System.out.println("Run.No." + i);
}
}
public static void main(String[] args){
//3.1 建立一個介面實現類物件
ImplementRunnable implementRunnable1 = new ImplementRunnable();
//3.2 建立執行緒物件,通過執行緒物件開啟執行緒(代理)
Thread thread = new Thread(implementRunnable1);
// 3.3呼叫執行緒start方法
thread.start();
// 3-歸併
new Thread(implementRunnable1).start();
for (int i = 0; i < 5; i++) {
System.out.println("No." + i);
}
}
}
2.3 Thread與Runnable的對比
2.3.1 繼承Thread類:
- 建立方法:子類繼承Thread類具備多執行緒能力
- 啟動執行緒方法:子類物件.start()
- 不建議使用:避免單繼承侷限性
2.3.2 實現Runnable介面:
- 實現介面Runnable具有多執行緒能力
- 啟動執行緒方法:new Thread(傳入目標物件).start()
- 推薦使用:避免了單繼承侷限性,靈活方便,方便同一物件被多個執行緒使用。
2.4 Callable介面(瞭解)
- 實現Callable介面,需返回值型別
- 重寫call方法,需丟擲異常
- 建立目標物件
- 建立執行服務:
ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交執行:
Future<Boolean> result1 = ser.submit(t1);
- 獲取結果:
boolean r1 = result1.get();
- 關閉服務:
ser.shutdownNow();
優缺點分析:
- 可以定義返回值
- 可以丟擲異常
- 實現方式較為複雜
3 靜態代理
示例:婚慶公司結婚模型(你去結婚,婚慶公司幫你結婚)
內容:
- 結婚介面Marry
- 你You:參與結婚的角色
- 婚慶公司WeddingCompany: 代理角色,幫你完成結婚這件事
- 比較Thread,婚慶公司~Thread, You~要呼叫的執行緒物件(實現runnable介面的類),thread代替介面實現類去做一些事情
new WeddingCompany(new You("Mike")).HappyMarry();
new Thread(() -> System.out.println("I love you!")).start();
模式總結:
- 真實物件和代理物件都要實現同一個介面
- 代理物件要代理真實角色
- 優勢
- 代理物件可以做很多真實物件不願做或無法做的事
- 真實物件專注做自己的事
4 執行緒狀態
執行緒可以處於以下狀態之一:
- NEW: 尚未啟動的執行緒處於此狀態。
- RUNNABLE:在Java虛擬機器中執行的執行緒處於此狀態。
- BLOCKED:被阻塞等待監視器鎖定的執行緒處於此狀態。
- WAITING:正在等待另一個執行緒執行特定動作的執行緒處於此狀態。
- TIMED_WAITING:正在等待另一個執行緒執行動作達到指定等待時間的執行緒處於此狀態。
- TERMINATED:已退出的執行緒處於此狀態。
一個執行緒可以在給定時間點處於一個狀態。 這些狀態是不反映任何作業系統執行緒狀態的虛擬機器狀態。
與此有關的一些執行緒方法
4.1 執行緒停止
- 不推薦JDK提供的stop,destroy方法[已過時 @Deprecated]
- 推薦執行緒自行停止(利用次數,不建議死迴圈)
- 建議使用標誌位進行終止變數,當flag=false則終止執行緒執行。
4.2 執行緒休眠(sleep)
- sleep(時間)指定當前執行緒阻塞的毫秒
- sleep存在異常InterruptedException
- sleep時間打倒後執行緒進入就緒狀態
- sleep可以模擬網路延時,倒數計時等
- 每個物件都有一個鎖,sleep不會釋放鎖
4.3 執行緒禮讓(yield)
概念: 讓當前正在執行的執行緒暫停,但不阻塞(將執行緒從執行狀態轉為就緒狀態)。
禮讓為讓CPU重新排程,因此禮讓成功與否取決於CPU。
4.4 執行緒強制執行(join)
Join合併執行緒,待此執行緒執行完成後再執行其他執行緒,其他執行緒阻塞。
類似於VIP插隊
4.5 執行緒狀態觀測(Thread.State)
執行緒狀態State為Thread類中的列舉型別:
public static enum Thread.State extends Enum<Thread.State>
獲取執行緒狀態的方法:
Thread.State state = thread.getState(); System.out.println(state);
4.6 執行緒優先順序
- Java提供一個執行緒排程器來監控程式中啟動後進入就緒狀態的所有執行緒,執行緒排程器按照優先順序決定該排程哪個執行緒來執行。
範圍:執行緒優先順序用數字表示[1, 10]
Thread.MIN_PRIORITY = 1; Thtead.MAX_PRIORITY = 10
改變/獲取執行緒優先順序:
getPriority(), setPriority(int xxx)
注:先設定優先順序再啟動;優先順序低只是意味著獲得排程的概率低。
4.7 守護執行緒(daemon)
- 執行緒分為使用者執行緒和守護執行緒
- 虛擬機器必須確保使用者執行緒執行完畢(main)
- 虛擬機器不用等待守護執行緒執行完畢(gc)
- 守護執行緒:後臺記錄操作日誌,監控記憶體,垃圾回收等