Java多線程-1(3)
本份隨記主要為狂神老師的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)
- 守護線程:後台記錄操作日誌,監控內存,垃圾回收等