Java基礎
多線程
進程
是一個正在執行中的程序。
每一個進程執行都有一個執行順序。該順序是一個執行路徑,或者叫一個控制單元。
線程
就是進程中的一個獨立的控制單元。
線程在控制着進程的執行。
一個進程中至少有一個線程。
Java VM 啓動的時候會有一個進程java.exe.
該進程中至少一個線程負責java程序的執行。
而且這個線程運行的代碼存在於main方法中。
該線程稱之爲主線程。
擴展:其實更細節說明jvm,jvm啓動不止一個線程,還有負責垃圾回收機制的線程。
1,如何在自定義的代碼中,自定義一個線程呢?
通過對api的查找,java已經提供了對線程這類事物的描述。就Thread類。
創建線程的第一種方式:繼承Thread類。
步驟:
1,定義類繼承Thread。
2,複寫Thread類中的run方法。
目的:將自定義代碼存儲在run方法。讓線程運行。
3,調用線程的start方法,
該方法兩個作用:啓動線程,調用run方法。
發現運行結果每一次都不同。
因爲多個線程都獲取cpu的執行權。cpu執行到誰,誰就運行。
明確一點,在某一個時刻,只能有一個程序在運行。(多核除外)
cpu在做着快速的切換,以達到看上去是同時運行的效果。
我們可以形象把多線程的運行行爲在互相搶奪cpu的執行權。
這就是多線程的一個特性:隨機性。誰搶到誰執行,至於執行多長,cpu說的算。
爲什麼要覆蓋run方法呢?
Thread類用於描述線程。
該類就定義了一個功能,用於存儲線程要運行的代碼。該存儲功能就是run方法。
也就是說Thread類中的run方法,用於存儲線程要運行的代碼。
線程運行狀態
獲取線程對象以及名稱
線程都有自己默認的名稱。
Thread-編號 該編號從0開始。
static Thread currentThread():獲取當前線程對象。
getName(): 獲取線程名稱。
設置線程名稱:setName或者構造函數。
創建線程的第二種方式:實現Runable接口
步驟:
1,定義類實現Runnable接口
2,覆蓋Runnable接口中的run方法。
將線程要運行的代碼存放在該run方法中。
3,通過Thread類建立線程對象。
4,將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數。
爲什麼要將Runnable接口的子類對象傳遞給Thread的構造函數。
因爲,自定義的run方法所屬的對象是Runnable接口的子類對象。
所以要讓線程去指定指定對象的run方法。就必須明確該run方法所屬對象。
5,調用Thread類的start方法開啓線程並調用Runnable接口子類的run方法。
實現方式和繼承方式有什麼區別呢?
實現方式好處:避免了單繼承的侷限性。
在定義線程時,建議使用實現方式。
兩種方式區別:
繼承Thread:線程代碼存放Thread子類run方法中。
實現Runnable,線程代碼存在接口的子類的run方法。
線程安全問題
多線程的運行出現了安全問題。
問題的原因:
當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,
另一個線程參與進來執行。導致共享數據的錯誤。
注:線程安全問題在理想狀態下,不容易出現,但一旦出現對軟件的影響是非常大的。
解決辦法:
對多條操作共享數據的語句,只能讓一個線程都執行完。在執行過程中,其他線程不可以參與執行。
同步(synchronized)
Java對於多線程的安全問題提供了專業的解決方式。
就是同步代碼塊。
synchronized(對象)
{
需要被同步的代碼
}
對象如同鎖。持有鎖的線程可以在同步中執行。
沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因爲沒有獲取鎖。
同步的前提:
1,必須要有兩個或者兩個以上的線程。
2,必須是多個線程使用同一個鎖。
必須保證同步中只能有一個線程在運行。
好處:解決了多線程的安全問題。
弊端:多個線程需要判斷鎖,較爲消耗資源,
同步函數
格式: 在函數上加上synchronized修飾符即可。
同步函數用的是哪一個鎖呢?
函數需要被對象調用。那麼函數都有一個所屬對象引用。就是this。
所以同步函數使用的鎖是this。
如果同步函數被靜態修飾後,使用的鎖是什麼呢?
通過驗證,發現不再是this。因爲靜態方法中也不可以定義this。
靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。
類名.class 該對象的類型是Class
靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象。 類名.class
死鎖
同步中嵌套同步