文章目錄
一、異常
異常: 程序在執行過程中,出現的非正常的情況,最終會導致JVM
的非正常停止。
- 對
Java
等面向對象的語言而言,異常本身是一個類,產生異常就是創建一個異常對象並拋出這個異常對象。Java
處理異常的方式是中斷處理。
1. 異常體系
- Throwable: 意思是可拋出的
- Error: 錯誤。不能避免的
- Exception: 異常。使用不當導致的,可以避免
- RuntimeException: 運行期異常
- Exception子類:
2. 異常的分類
異常: 程序得了小病,處理完即可
- Exception: 編譯期異常(不處理,編譯就不能通過)
- RuntimeException: 運行期異常(一般是由程序邏輯引起的錯誤)
錯誤: 程序得了大病,必須修改源代碼
編譯異常的處理:
- 交給虛擬機處理,直接拋出異常
- 選中代碼,按
Alt 回車
,選擇try/catch
。(後續代碼會繼續執行)
運行期異常:
錯誤:
3. 異常的產生過程
JVM檢測出異常後,會做兩件事:
JVM
會根據異常產生的原因,創建一個異常對象,這個異常對象包括(內容,原因,位置)- 如果沒找到異常處理邏輯(try…catch),就會把異常傳遞給調用者。(如果調用者也沒有異常處理邏輯 ,就一路向上找,直到找到異常處理邏輯 。如果實在沒有,就交給
JVM
處理)(其實很好理解,第二個調用調用者一般是main
,第一個調用者一定是JVM
)
JVM接收到了異常對象,會做兩件事:(JVM默認處理)
- 以紅色字體,把這個異常打印在控制檯
- 終止當前程序
4. 異常的處理
Java
異常處理的五關鍵字:try、catch、finally、throw、throws
4.1 throw
throw new xxxException("異常產生的原因");
注意:
- throw關鍵字必須放在方法內部
- new的對象,必須是Eception或其子類對象
- 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
throws
關鍵字必須寫在方法聲明處throws
後邊聲明的異常,必須是Exception
或其子類- 如果方法內部拋出多個異常,那麼
throws
必須聲明多個異常(如果拋出的異常,有子父類的關係,拋出父類異常即可) - 如果調用了
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
無論是否出現異常都會執行
finally
必須和try
一起使用finally
一般用於資源釋放
try {
// 這裏如果拋出了異常,就會交給catch處理
} catch(類型1 參數1) { // 可以有多個catch
// 處理1
} finally {
// 資源回收
}
注意: finally
裏如果有return語句,返回給調用處的,一定是finally
裏的return
結果(比如try
和finally
中,都有return
,會返回finally
中的)
4.7 多異常捕獲
- 多個異常分別處理(用多個
try...catch
) - 多個異常一次捕獲,多次處理(用一個
try
,多個catch
)
catch
裏定義的異常變量,如果有子父類關係,那麼catch
裏面定義的異常變量,必須寫在上面,否則會報錯(catch
裏的參數指向了這個錯誤對象) - 多個異常一次捕獲,一次處理(用一個
try
,catch
能接收的是裏面所有報錯的父類異常)
4.8 子父類異常
父類異常怎麼樣,子類異常就怎麼樣。
- 父類拋出多個異常,子類也拋出相同的異常。
或拋出父類異常的子類
或不拋出異常 - 父類沒有拋出異常,子類就不可以拋出異常(必須解決掉,不能聲明拋出)
4.9 自定義異常類
- 繼承自
Exception
或RuntimeException
- 一個空參數的構造方法
- 一個帶提示信息的構造方法
可以使用
Alt Enter
,直接使用父類重載形式
注意: 自定義異常類,類名要以Exception結尾(軟性規定,表示其是一個異常類)
Demo:模擬註冊操作
- 使用數組保存註冊過的用戶名(以後存到數據庫)
- 使用
Scanner
獲取用戶輸入的註冊的用戶名(以後用表單) - 定義一個方法,對註冊的用戶名進行判斷,如果用戶名已經註冊過,拋出註冊異常
二、多線程
- 併發:(交替執行) 多件事情,在同一時間段進行
- 並行:(同時執行) 多件事情,在同一時間點進行
一個核心,只能同時處理一件事,那如何進行多任務?方法一是增加核心數,方法二是在進程間快速切換,快到我們無法察覺。後者就叫併發
1. 進程與線程
-
進程: 即進行中的程序,是把硬盤中的資源和命令複製到內存中,然後通過命令操作對應的資源。
-
線程: 一個執行單元,負責進程的執行。
-
這是
HelloWorld.s
文件(彙編語言,看不懂沒關係)。這樣的一份文件,也叫程序,存在硬盤上(準確來說應該是.exe
叫程序)
-
當點擊
HelloWorld.exe
時,程序被複制到內存中開始運行,這就叫進程
-
代碼一行行執行,我們想象有這樣一個箭頭,從上向下走。一個箭頭就是一個線程(線程是應用程序與CPU間的執行路徑,CPU可以在多個路徑間做快速切換。)
2. 線程的調度
線程的調度方式有兩種:
- 分時調度: 輪流使用CPU的使用權,平均分配每個線程佔用CPU的時間。
- 搶佔式調度: 根據優先級進行調度,優先級高的優先使用CPU。(如果優先級相同,會隨機選擇一個,這稱爲線程隨機性 ),
Java
的使用爲搶佔式調度 。
3. 主線程
JVM
執行main()
方法,main()
方法會執行到棧內存JVM
會找操作系統,開闢一條通往CPU
的執行路徑CPU
就可以通過這個路徑來執行main()
方法
這條路徑就叫做主線程
- 原始程序:
- 改造後程序: 我們讓主線程拋異常,看看發生啥。會發現,後面的代碼沒執行。
4. 創建多線程的第一種方式
創建多線程的第一種方式:創建Thread的子類
- 創建一個
Thread
的子類 - 在這個子類中重寫
run
方法,設置線程任務(開啓線程要做什麼) - 創建這個子類對象
- 調用
start
方法,開啓新線程,來執行run
方法
兩個線程指的是:調用它的線程(這裏指main線程
)和新創建的線程。start
只能調用一次,多次調用是會報錯的。
- 類文件中:
- 執行文件中:
多次調用
start
時,會拋出** 非法線程start異常**
4.2 獲取線程名稱
- 方法1: 使用
Thread
類中的方法getName()
- 方法2: 使用靜態方法
Thread.currentThread()
獲取當前線程名
Thread.currentThread()
使用getName()
4.3 改變線程名稱
- 方法1: 使用
setName()
方法
- 方法2: 給父類傳遞一個帶參數
name
的構造方法
4.4 sleep方法
sleep
方法可以讓程序暫停,參數是(long 毫秒值)。注意,它是靜態方法。(本身帶異常,所以使用try..catch
,當然,你也可以拋出)
5. 多線程內存圖
多線程的原理是開闢多個棧空間
5.1 單線程程序
先寫一個單線程程序: T
類繼承自Thread
類
main()
方法進棧,逐行執行,執行到第一行,在堆中創建一個對象
- 調用
run()
方法,載入棧中
這就是單線程程序
5.2 多線程程序
main()
方法進棧,逐行執行,執行到第一行,在堆中創建一個對象
- 當執行到
start()
方法時,會開闢一個新的棧空間,同時這個新的棧空間裏,放着run()
方法
多個線程互不影響,
6. 創建多線程的第二種方式
聲明實現Runnable
接口的類,然後實現run()
。然後分配該類的實例,在創建Thread
時,作爲一個參數來傳遞並啓動。
- 創建一個
Runnable
的實現類,要重寫run
方法 - 將這個實現類對象當參數傳遞
- 調用
start()
方法
- 代碼:
- 結果:
6.1 兩種方式的對比
- 直接創建Thread類子類: 步驟少,一次性程序使用
- 利用Runnable接口實現:
- 避免了單繼承 的侷限性
- 增強了程序的擴展性,進行了解耦