1. JAVA多線程內存架構
要想深入理解JAVA多線程的機制,必須要從JVM的內存管理機制說起。
1.1 典型的JAVA多線程內存管理機制:
JVM將運行時內存按功能分爲以下三個部分:堆(heap),棧(stack)和方法區(Method Area)。
- 方法區主要存放每個類編譯後的字節碼,它包含每個類的全部信息,包括類的屬性,靜態方法和一般方法等,還包含字符串等靜態內容;
- 堆主要存放根據方法區中的類實例化(new)出的對象和數組(數組也可以被看作對象的一種形式),該對象擁有該類的初始化對象屬性,以及該類的方法指針(指向方法區中類的對應方法),堆中的對象都是線程共享的,也即所有線程都能使用堆中的對象;
- 棧由多個線程組成,每個線程都保存由當前線程的運行上下文,緩存數據,方法內臨時變量等;
1.2模擬執行過程:
- JVM加載當前含main靜態方法的類到方法區,並在棧中創建main線程;
- main線程中加載了main靜態方法,該方法在當前main線程中完成創建對象1的操作:
Class1 class1 = new Class1();
//從方法區中複製類1的屬性到堆中創建的對象1d的內存區域,完成初始化工作;
//main線程中僅保存class1這樣一個對象指針指向堆中的對象1所在內存空間;對象1中方法指針指向方法區類1的方法內容;
- main線程中創建了新線程,線程1,並按以上方法實例化出對象2;
- CPU根據多個線程的優先級隨機接入要處理的線程。
由以上CPU輪詢的過程可以看出多線程並不是真正意義上的多個線程並行執行(GPU是真正意義上的並行),而是CPU在多個線程任務之間切換,不至於卡死在一個線程任務上。
1.3多線程初探:
由於堆中的對象是公開透明的,也即任何一個線程都可以操作,此時如果兩個線程調用了堆中同一個對象的同一個方法(去修改同一個屬性的值),必定會引起衝突。
因而多線程問題也即是多個線程爭同一對象或同一類中的屬性和方法。
爲了保證運行時不衝突(操作的原子性),JAVA選擇給 方法加鎖 的形式完成同步工作(也即排隊進入該方法,避免衝突)。
- 鎖:不同方法可以加相同的鎖,也可以加不同的鎖,但一種鎖只有一個鑰匙;
- 鑰匙:哪個線程在執行過程中先運行到鎖處就先獲得鑰匙,其他線程沒有鑰匙不得進入該種鎖限制的所有方法;
鎖和鑰匙在使用過程中也必須滿足: - N個線程只能有1個能獲得當前對象的當前種類鎖的鑰匙;
- N個線程可以同時分別獲得同一對象的N種鎖的鑰匙;
- 針對無鎖的方法,任何線程都可以隨意進出執行。
2. 多線程建立方法
JAVA編程實現多線程的常用方法有兩種:
- 繼承Thread類,並覆蓋run()方法
- 實現Runnable接口,並覆蓋run()方法
注意,這裏需要說明:
- Thread類本身已經實現了Runnable接口;
- 上圖B方法中MyThread類首先實現了Runnable接口,再講其實例對象傳入Thread類的構造器中;
總的來說,開啓新線程的方法就是要先構造Thread類或其子類的實例對象,該對象代表着新線程。
3. 線程的生命週期
JAVA中每個線程都有自己的生命週期,也即線程當前所處的狀態;
3.1常見線程狀態:
New狀態:新創建線程;
Runnable狀態:可執行狀態,在該狀態下的線程等待被CPU輪詢執行;
Running狀態:正在使用CPU資源;
Waiting狀態:等待狀態;
Blocked狀態:線程阻塞狀態,當前線程沒有獲得鑰匙,只能阻塞等待到有鑰匙才能執行;
Terminate狀態:線程處理結束,被銷燬
3.2常用線程控制方法:
static currentThread(); 返回當前線程對象
static sleep(); 使當前線程休眠,注意該方法爲靜態方法,不能對實例使用
interrupt(); 設置中斷標誌位,但不像break那樣啓動中斷;
static interrupted(); 判斷當前線程是否中斷,靜態方法,對實例使用無效;
該方法具有清除狀態功能,第一次調用會消除中斷標誌,因而下次調用顯示false
isInterrupted(); 判斷當前實例線程是否中斷,對線程對象判斷
static yield(); 靜態方法,暫時放棄CPU資源,讓其他線程使用
isAlive(); 判斷當前線程是否爲存活,正在執行或者正準備執行爲存活狀態
getName(); 獲得線程名字;
getId();獲得線程唯一標識;
setPriority(); 設置線程優先級,最高爲10,最低爲1,
getPriority(); 獲取當前實例線程的優先級,優先級具有繼承性,規則性,隨機性
4. 線程優先級
線程具有優先級,最高爲10,最低爲1。
線程的優先級具有三個特性:
- 繼承性:於線程1中創建新線程2,線程2具有和線程1一樣的優先級,但可重新設定優先級;
- 規則性: 優先級高的線程可獲得更多的CPU資源;
- 隨機性: 優先級高的並不一定先執行,而是表示有更多的概率獲得CPU資源。
5. synchronized關鍵字
5.1鎖的分類
- 一般方法:無鎖,任何線程都可通知執行;
- 對象鎖方法:使用對象作爲鎖的標誌
格式:
synchronized(this){
//被加鎖的代碼塊,其中鎖this可以被替換成任何對象
}
public synchronized void method(){
//被加鎖的方法內容
}
- 類鎖方法:使用類作爲鎖的標誌
synchronized(xxx.class){}
synchronized(Class.forName(“xxx”)){}
public static synchronized void method(){}
5.2對象鎖和類鎖的區別:
- 對象鎖應用在多個線程爭用同一對象內的被synchronized修飾的非靜態方法,會有同步效果;
- 類鎖應用在多個線程爭用同一個類的被synchronized修飾的靜態方法,會有同步效果;
- 對象鎖爭用的是堆內的同一對象的方法,類鎖是爭用方法區的同一類的靜態方法。
5.3鎖的使用規則:
- 一類鎖只有一把鑰匙,先執行的線程獲得當前類鎖的鑰匙,執行完交出鑰匙,其餘阻塞線程再爭取;
- 當前線程爭取得鑰匙後,可以繼續開啓其他被當前鎖控制的方法,直到執行完畢交出鑰匙;
- 在對象鎖中,多個線程爭一個對象的某類鎖的鑰匙纔會同步;
- 多個線程爭一個對象的不同類鎖的鑰匙不會同步;
- 多個線程爭多個對象的同一類鎖的鑰匙不會同步,
- 在類鎖中,分屬多個對象的線程爭取當前類的靜態方法會同步