Java多線程-1(3)

本份隨記主要為狂神老師的Java多線程教學的學習筆記,記載了視頻中一些有關基礎概念以及部分代碼示例。隨機分為1-3共三份,知識點記錄的不是很深入,以後的學習過程中隨時補充。

1 有關基礎概念

1.1 核心概念

  1. 線程就是獨立的執行路徑
  2. 程序運行時,即使沒有自己創建線程,後台也會由多個線程(主線程、gc線程)
  3. main()稱之為主線程,為系統的入口,用於執行整個程序
  4. 一個進程中,若開闢多個線程,線程的運行由調度器安排調度,調度器與操作系統緊密相關,先後順序不能被人為干預。
  5. 對同一份資源操作時,會存在資源搶奪問題,需加入並發控制
  6. 線程會帶來額外的開銷(cpu調度時間,並發控制開銷)
  7. 每個線程在自己的工作內存交互,內存控制不當會造成數據不一致(隊列和鎖)。

1.2 程序、進程、線程

  1. 程序(靜態概念):程序是指令和數據的有序集合,本身沒有任何運行的含義
  2. 進程(動態概念):程序的一次執行過程,是系統資源分配的單位
  3. 線程:CPU調度和分派的基本單位。(進程中可包含至少一個的若干線程)
  4. 進程 vs 線程:
    同一進程中的線程使用相同的地址空間,而不同的進程則不會。這允許線程讀寫公共共享和數據結構和變量,也增加了線程之間的通信。然而,進程間通信(即IPC)是非常困難的,並且需要耗費大量資源。

1.3 並發、並行

  1. 並行:兩個或多個事務在同一時刻發生。
    實際多線程為並行執行:多個cpu(多核)共同執行線程。
  2. 並發:兩個或多個事務在同一時間間隔內發生。
    模擬多線程為並發執行:在一個cpu情況下同一時間點只能執行一個線程,但切換很快。

2 線程創建

2.1 Thread class(繼承Thread類)

  1. 自定義線程類繼承Thread類
  2. 子類重寫父類的run方法,編寫程序執行體
  3. 分配並啟動子類的實例(創建線程對象,調用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接口的實現

  1. 定義MyRunnable類實現Runnable接口
  2. 實現run方法,編寫程序執行體
  3. 創建線程對象(代理),調用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類:

  1. 創建方法:子類繼承Thread類具備多線程能力
  2. 啟動線程方法:子類對象.start()
  3. 不建議使用:避免單繼承局限性

2.3.2 實現Runnable接口:

  1. 實現接口Runnable具有多線程能力
  2. 啟動線程方法:new Thread(傳入目標對象).start()
  3. 推薦使用:避免了單繼承局限性,靈活方便,方便同一對象被多個線程使用。

2.4 Callable接口(了解)

  1. 實現Callable接口,需返回值類型
  2. 重寫call方法,需拋出異常
  3. 創建目標對象
  4. 創建執行服務:ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交執行:Future<Boolean> result1 = ser.submit(t1);
  6. 獲取結果:boolean r1 = result1.get();
  7. 關閉服務:ser.shutdownNow();

優缺點分析:

  1. 可以定義返回值
  2. 可以拋出異常
  3. 實現方式較為複雜

3 靜態代理

示例:婚慶公司結婚模型(你去結婚,婚慶公司幫你結婚)
內容:

  1. 結婚接口Marry
  2. 你You:參與結婚的角色
  3. 婚慶公司WeddingCompany: 代理角色,幫你完成結婚這件事
  4. 比較Thread,婚慶公司~Thread, You~要調用的線程對象(實現runnable接口的類),thread代替接口實現類去做一些事情
    new WeddingCompany(new You("Mike")).HappyMarry();
    new Thread(() -> System.out.println("I love you!")).start();

模式總結:

  1. 真實對象和代理對象都要實現同一個接口
  2. 代理對象要代理真實角色
  3. 優勢
    1. 代理對象可以做很多真實對象不願做或無法做的事
    2. 真實對象專註做自己的事

4 線程狀態

線程可以處於以下狀態之一:

  1. NEW: 尚未啟動的線程處於此狀態。
  2. RUNNABLE:在Java虛擬機中執行的線程處於此狀態。
  3. BLOCKED:被阻塞等待監視器鎖定的線程處於此狀態。
  4. WAITING:正在等待另一個線程執行特定動作的線程處於此狀態。
  5. TIMED_WAITING:正在等待另一個線程執行動作達到指定等待時間的線程處於此狀態。
  6. TERMINATED:已退出的線程處於此狀態。

一個線程可以在給定時間點處於一個狀態。 這些狀態是不反映任何操作系統線程狀態的虛擬機狀態。

image


與此有關的一些線程方法
image

4.1 線程停止

  1. 不推薦JDK提供的stop,destroy方法[已過時 @Deprecated]
  2. 推薦線程自行停止(利用次數,不建議死循環)
  3. 建議使用標誌位進行終止變量,當flag=false則終止線程運行。

4.2 線程休眠(sleep)

  1. sleep(時間)指定當前線程阻塞的毫秒
  2. sleep存在異常InterruptedException
  3. sleep時間打倒後線程進入就緒狀態
  4. sleep可以模擬網絡延時,倒計時等
  5. 每個對象都有一個鎖,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)

  1. 線程分為用戶線程和守護線程
  2. 虛擬機必須確保用戶線程執行完畢(main)
  3. 虛擬機不用等待守護線程執行完畢(gc)
  4. 守護線程:後台記錄操作日誌,監控內存,垃圾回收等