Java語言進階 #異常&多線程 #Day16 #異常 #創建/使用多線程

一、異常

異常: 程序在執行過程中,出現的非正常的情況,最終會導致JVM的非正常停止。

  • Java等面向對象的語言而言,異常本身是一個類,產生異常就是創建一個異常對象並拋出這個異常對象。Java處理異常的方式是中斷處理。

1. 異常體系

Throwable
Error
Exception
RuntimeException
  • Throwable: 意思是可拋出的
  • Error: 錯誤。不能避免的
  • Exception: 異常。使用不當導致的,可以避免
  • RuntimeException: 運行期異常

  • Exception子類:
    在這裏插入圖片描述

2. 異常的分類

異常: 程序得了小病,處理完即可

  • Exception: 編譯期異常(不處理,編譯就不能通過)
  • RuntimeException: 運行期異常(一般是由程序邏輯引起的錯誤)

錯誤: 程序得了大病,必須修改源代碼


編譯異常的處理:

  1. 交給虛擬機處理,直接拋出異常
    在這裏插入圖片描述
  2. 選中代碼,按Alt 回車,選擇try/catch。(後續代碼會繼續執行)
    在這裏插入圖片描述
    在這裏插入圖片描述

運行期異常:
在這裏插入圖片描述


錯誤:
在這裏插入圖片描述

3. 異常的產生過程

JVM檢測出異常後,會做兩件事:

  1. JVM會根據異常產生的原因,創建一個異常對象,這個異常對象包括(內容,原因,位置)
  2. 如果沒找到異常處理邏輯(try…catch),就會把異常傳遞給調用者。(如果調用者也沒有異常處理邏輯 ,就一路向上找,直到找到異常處理邏輯 。如果實在沒有,就交給JVM處理)(其實很好理解,第二個調用調用者一般是main,第一個調用者一定是JVM

JVM接收到了異常對象,會做兩件事:(JVM默認處理)

  1. 以紅色字體,把這個異常打印在控制檯
  2. 終止當前程序

4. 異常的處理

Java異常處理的五關鍵字:try、catch、finally、throw、throws

4.1 throw

throw new xxxException("異常產生的原因");

注意:

  1. throw關鍵字必須放在方法內部
  2. new的對象,必須是Eception或其子類對象
  3. throw關鍵字拋出的指定的異常對象,我們就必須處理這個異常對象
      3.1 如果創建的是運行期異常, 可以不處理,交給JVM默認處理
      3.2 如果創建的是編譯期異常,要麼throws(交給別人處理), 要麼try...catch(自己處理)

參數的合法性效驗: (補充知識,日後在工作中),如果參數不合法,我們就必須用拋出異常的方式,告訴方法的調用者(傳遞的參數有問題)
在這裏插入圖片描述
在這裏插入圖片描述

  • 空指針異常屬於運行期異常,默認給JVM處理即可。(索引越界異常也是運行期異常)
    在這裏插入圖片描述

4.2 Objects非空的判斷

  • 先自己寫一個判斷:
    在這裏插入圖片描述
  • Objects中有專門處理空指針異常的:
    在這裏插入圖片描述
    在這裏插入圖片描述

4.3 throws

throws是異常處理的第一種方式:交給別人處理(把異常交給調用者,最後交給JVM

修飾符 返回值類型 方法名(參數列表) throws 異常1,異常2... {
	// 方法體;
}

注: 有沒有覺得很熟悉? 在2.異常的分類中,那個main方法後面被自動地加上了throws

  1. throws關鍵字必須寫在方法聲明處
  2. throws後邊聲明的異常,必須是Exception或其子類
  3. 如果方法內部拋出多個異常,那麼throws必須聲明多個異常(如果拋出的異常,有子父類的關係,拋出父類異常即可)
  4. 如果調用了throws聲明拋出,必須自己處理(try…catch),或交給JVM處理(繼續throws

在這裏插入圖片描述

4.4 捕獲異常

一般在工作中,會把異常記錄到工作日誌

try {
  // 這裏如果拋出了異常,就會交給catch處理
} catch(類型1 參數1) {  // 可以有多個catch
  // 處理1
} catch(類型2 參數2) {
  // 處理2
}

在這裏插入圖片描述

4.5 三個異常處理辦法

  • getMessage(): 簡短描述
    在這裏插入圖片描述
  • toString(): 詳細描述
    在這裏插入圖片描述
  • printStackTrace(): 直接打印詳細信息的方法
    在這裏插入圖片描述

4.6 finally

無論是否出現異常都會執行

  1. finally必須和try一起使用
  2. finally一般用於資源釋放
try {
  // 這裏如果拋出了異常,就會交給catch處理
} catch(類型1 參數1) {  // 可以有多個catch
  // 處理1
} finally {
  // 資源回收
}

在這裏插入圖片描述

注意: finally裏如果有return語句,返回給調用處的,一定是finally裏的return結果(比如tryfinally中,都有return,會返回finally中的)

4.7 多異常捕獲

  1. 多個異常分別處理(用多個try...catch
  2. 多個異常一次捕獲,多次處理(用一個try,多個catch
    catch裏定義的異常變量,如果有子父類關係,那麼catch裏面定義的異常變量,必須寫在上面,否則會報錯(catch裏的參數指向了這個錯誤對象)
  3. 多個異常一次捕獲,一次處理(用一個trycatch能接收的是裏面所有報錯的父類異常)

4.8 子父類異常

父類異常怎麼樣,子類異常就怎麼樣。

  1. 父類拋出多個異常,子類也拋出相同的異常。
      或拋出父類異常的子類
      或不拋出異常
  2. 父類沒有拋出異常,子類就不可以拋出異常(必須解決掉,不能聲明拋出)

在這裏插入圖片描述

4.9 自定義異常類

  1. 繼承自ExceptionRuntimeException
  2. 一個空參數的構造方法
  3. 一個帶提示信息的構造方法

可以使用Alt Enter,直接使用父類重載形式

注意: 自定義異常類,類名要以Exception結尾(軟性規定,表示其是一個異常類)

Demo:模擬註冊操作

  1. 使用數組保存註冊過的用戶名(以後存到數據庫)
  2. 使用Scanner獲取用戶輸入的註冊的用戶名(以後用表單)
  3. 定義一個方法,對註冊的用戶名進行判斷,如果用戶名已經註冊過,拋出註冊異常

在這裏插入圖片描述

二、多線程

  • 併發:(交替執行) 多件事情,在同一時間段進行
  • 並行:(同時執行) 多件事情,在同一時間點進行

一個核心,只能同時處理一件事,那如何進行多任務?方法一是增加核心數,方法二是在進程間快速切換,快到我們無法察覺。後者就叫併發

1. 進程與線程

  • 進程: 即進行中的程序,是把硬盤中的資源和命令複製到內存中,然後通過命令操作對應的資源。

  • 線程: 一個執行單元,負責進程的執行。

  • 這是HelloWorld.s文件(彙編語言,看不懂沒關係)。這樣的一份文件,也叫程序,存在硬盤上(準確來說應該是.exe叫程序)
    在這裏插入圖片描述

  • 當點擊HelloWorld.exe時,程序被複制到內存中開始運行,這就叫進程
    在這裏插入圖片描述

  • 代碼一行行執行,我們想象有這樣一個箭頭,從上向下走。一個箭頭就是一個線程(線程是應用程序與CPU間的執行路徑,CPU可以在多個路徑間做快速切換。)
    在這裏插入圖片描述

2. 線程的調度

線程的調度方式有兩種:

  1. 分時調度: 輪流使用CPU的使用權,平均分配每個線程佔用CPU的時間。
  2. 搶佔式調度: 根據優先級進行調度,優先級高的優先使用CPU。(如果優先級相同,會隨機選擇一個,這稱爲線程隨機性 ),Java的使用爲搶佔式調度

3. 主線程

  1. JVM執行main()方法,main()方法會執行到棧內存
  2. JVM會找操作系統,開闢一條通往CPU的執行路徑
  3. CPU就可以通過這個路徑來執行main()方法

這條路徑就叫做主線程


  • 原始程序:
    在這裏插入圖片描述
  • 改造後程序: 我們讓主線程拋異常,看看發生啥。會發現,後面的代碼沒執行。
    在這裏插入圖片描述

4. 創建多線程的第一種方式

創建多線程的第一種方式:創建Thread的子類

  1. 創建一個Thread的子類
  2. 在這個子類中重寫run方法,設置線程任務(開啓線程要做什麼)
  3. 創建這個子類對象
  4. 調用start方法,開啓新線程,來執行run方法
    在這裏插入圖片描述
    兩個線程指的是:調用它的線程(這裏指main線程)和新創建的線程。start只能調用一次,多次調用是會報錯的。

  • 類文件中:
    在這裏插入圖片描述
  • 執行文件中:
    在這裏插入圖片描述

多次調用start時,會拋出** 非法線程start異常**
在這裏插入圖片描述

4.2 獲取線程名稱

  1. 方法1: 使用 Thread類中的方法getName()
    在這裏插入圖片描述
  2. 方法2: 使用靜態方法Thread.currentThread()獲取當前線程名
    在這裏插入圖片描述
    Thread.currentThread()使用getName()
    在這裏插入圖片描述

4.3 改變線程名稱

  1. 方法1: 使用setName()方法
    在這裏插入圖片描述
  2. 方法2: 給父類傳遞一個帶參數name的構造方法
    在這裏插入圖片描述

4.4 sleep方法

sleep方法可以讓程序暫停,參數是(long 毫秒值)。注意,它是靜態方法。(本身帶異常,所以使用try..catch,當然,你也可以拋出)
在這裏插入圖片描述

5. 多線程內存圖

多線程的原理是開闢多個棧空間

5.1 單線程程序

先寫一個單線程程序: T類繼承自Thread
在這裏插入圖片描述

  1. main()方法進棧,逐行執行,執行到第一行,在堆中創建一個對象
    在這裏插入圖片描述
  2. 調用run()方法,載入棧中
    在這裏插入圖片描述

這就是單線程程序

5.2 多線程程序

  1. main()方法進棧,逐行執行,執行到第一行,在堆中創建一個對象
    在這裏插入圖片描述
  2. 當執行到start()方法時,會開闢一個新的棧空間,同時這個新的棧空間裏,放着run()方法
    在這裏插入圖片描述

多個線程互不影響,

6. 創建多線程的第二種方式

聲明實現Runnable接口的類,然後實現run()。然後分配該類的實例,在創建Thread時,作爲一個參數來傳遞並啓動。

  1. 創建一個Runnable的實現類,要重寫run方法
  2. 將這個實現類對象當參數傳遞
  3. 調用start()方法

  • 代碼:
    在這裏插入圖片描述
  • 結果:
    在這裏插入圖片描述

6.1 兩種方式的對比

  • 直接創建Thread類子類: 步驟少,一次性程序使用
  • 利用Runnable接口實現:
  1. 避免了單繼承 的侷限性
  2. 增強了程序的擴展性,進行了解耦

6.2 使用匿名內部類的方式

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章