1. 程式人生 > >synchronized實現快三原始碼出售原理

synchronized實現快三原始碼出售原理

一、synchronized用法
Java中的快三原始碼出售QQ2952777280【話仙原始碼論壇】hxforum.com 同步塊用synchronized標記。

同步塊在Java中是同步在某個物件上(監視器物件)。

所有同步在一個物件上的同步塊在同時只能被一個執行緒進入並執行操作。

所有其他等待進入該同步塊的執行緒將被阻塞,直到執行該同步塊中的執行緒退出。

(注:不要使用全域性物件(常量等)做監視器。應使用唯一對應的物件)

複製程式碼
public class MyClass {
int count;

// 1.例項方法
public synchronized void add(int value){
    count += value;
}

// 2.例項方法中的同步塊 (等價於1)
public void add(int value){
    synchronized(this){
        count += value;
    }
}

// 3.靜態方法
public static synchronized void add(int value){
     count += value;
}

// 4.靜態方法中的同步塊 (等價於3)
public static void add(int value){
    synchronized(MyClass.class){
        count += value;
    }
}

}
複製程式碼
二、Java物件模型
每一個Java類,在被JVM載入的時候,JVM會給這個類建立一個instanceKlass,儲存在方法區,用來在JVM層表示該Java類。

使用new建立一個物件的時候,JVM會建立一個instanceOopDesc物件,這個物件中包含了兩部分資訊,物件頭以及例項資料。

物件頭中包括兩部分:(一)Mark Word 一些執行時資料,其中就包括和多執行緒相關的鎖的資訊。

(二)Klass Point 元資料其實維護的是指標,指向的是物件所屬的類的instanceKlass。

Mark Word 用於儲存物件自身的執行時資料,如雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒 ID、偏向時間戳等等。

物件頭資訊是與物件自身定義的資料無關的額外儲存成本,但是考慮到虛擬機器的空間效率,Mark Word被設計成一個非固定的資料結構以便在極小的空間記憶體儲存儘量多的資料,它會根據物件的狀態複用自己的儲存空間,也就是說,Mark Word會隨著程式的執行發生變化。

物件儲存結構:

物件的例項(instantOopDesc)儲存在堆上,物件的元資料(instantKlass)儲存在方法區,物件的引用儲存在棧上。

三、Moniter
為了解決執行緒安全的問題,Java提供了同步機制、互斥鎖機制,這個機制保證了在同一時刻只有一個執行緒能訪問共享資源。這個機制的保障來源於監視鎖Monitor。

每一個Object物件中內建了一個Monitor物件。(物件頭的MarkWord中的LockWord指向monitor的起始地址)

Monitor相當於一個許可證,執行緒拿到許可證即可以進行操作,沒有拿到則需要阻塞等待。

ObjectMonitor中有幾個關鍵屬性:

_owner:指向持有ObjectMonitor物件的執行緒

_WaitSet:存放處於wait狀態的執行緒佇列

_EntryList:存放處於等待鎖block狀態的執行緒佇列

_recursions:鎖的重入次數

_count:用來記錄該執行緒獲取鎖的次數

執行緒T等待物件鎖:_EntryList中加入T

執行緒T獲取物件鎖:_EntryList移除T,_owner置為T,計數器_count+1

持有物件鎖的執行緒呼叫wait():_owner置為T,計數器_count+1,_WaitSet中加入T

四、synchronized原理
利用javap工具檢視生成的class檔案資訊來分析Synchronize的實現

(摘自: 【死磕Java併發】—–深入分析synchronized的實現原理)

複製程式碼
public class SynchronizedTest {
public synchronized void test1(){

}

public void test2(){
    synchronized (this){

    }
}

}
複製程式碼

同步程式碼塊:monitorenter指令插入到同步程式碼塊的開始位置,monitorexit指令插入到同步程式碼塊的結束位置,JVM需要保證每一個monitorenter都有一個monitorexit與之相對應。任何物件都有一個monitor與之相關聯,當且一個monitor被持有之後,他將處於鎖定狀態。執行緒執行到monitorenter指令時,將會嘗試獲取物件所對應的monitor所有權,即嘗試獲取物件的鎖;
同步方法:synchronized方法則會被翻譯成普通的方法呼叫和返回指令如:invokevirtual、areturn指令,在VM位元組碼層面並沒有任何特別的指令來實現被synchronized修飾的方法,而是在Class檔案的方法表中將該方法的access_flags欄位中的synchronized標誌位置1,表示該方法是同步方法並使用呼叫該方法的物件或該方法所屬的Class在JVM的內部物件表示Klass做為鎖物件。

五、鎖優化
Java的執行緒是對映到作業系統原生執行緒之上的,如果要阻塞或喚醒一個執行緒就需要作業系統的幫忙,這就要從使用者態轉換到核心態,因此狀態轉換需要花費很多的處理器時間,是java語言中一個重量級的操縱。

重量級鎖:通過物件內部的監視器(monitor)實現,其中monitor的本質是依賴於底層作業系統的Mutex Lock實現,作業系統實現執行緒之間的切換需要從使用者態到核心態的切換,切換成本非常高。

在JDK1.6中出現對鎖進行了很多的優化,進而出現輕量級鎖,偏向鎖,鎖消除,適應性自旋鎖,鎖粗化。

自旋鎖
共享資料的鎖定狀態一般只會持續很短的一段時間,為了這段時間去掛起和恢復執行緒其實並不值得。

讓後面來的執行緒“稍微等一下”,但是並不放棄處理器的執行時間,看看持有鎖的執行緒會不會很快釋放鎖。這個“稍微等一下”的過程就是自旋。(怎麼等待呢?執行一段無意義的迴圈即可)

1、由於自旋鎖只是將當前執行緒不停地執行迴圈體,不進行執行緒狀態的改變,所以響應速度更快。

2、但當執行緒數不停增加時,效能下降明顯,因為每個執行緒都需要執行,佔用CPU時間。

3、自旋鎖和阻塞鎖最大的區別就是,到底要不要放棄處理器的執行時間。阻塞鎖是放棄了CPU時間,進入了等待區,等待被喚醒。而自旋鎖是一直“自旋”在那裡,時刻的檢查共享資源是否可以被訪問。

鎖消除
JVM檢測到不可能存在共享資料競爭,這是JVM會對這些同步鎖進行鎖消除。鎖消除的依據是逃逸分析的資料支援。

注意:我們在使用一些JDK的內建API時,如StringBuffer、Vector、HashTable等,這個時候會存在隱形的加鎖操作。

複製程式碼
// 在執行這段程式碼時,JVM可以明顯檢測到變數vector沒有逃逸出方法vectorTest()之外,所以JVM可以大膽地將vector內部的加鎖操作消除。
public void vectorTest() {
Vector vector = new Vector();
for (int i = 0; i < 10; i++) {
vector.add(i + “”);// vector是執行緒安全的,每個方法都有synchronized修飾
}
System.out.println(vector);
}
複製程式碼
鎖粗化
我們提倡儘量減小鎖的粒度:使用同步鎖的時候,需要讓同步塊的作用範圍儘可能小—僅在共享資料的實際作用域中才進行同步,這樣做的目的是為了使需要同步的運算元量儘可能縮小,如果存在鎖競爭,那麼等待鎖的執行緒也能儘快拿到鎖。

問題:如果一系列的連續加鎖解鎖操作,可能會導致不必要的效能損耗。

鎖粗化:將多個連續的加鎖、解鎖操作連線在一起,擴充套件成一個範圍更大的鎖。

複製程式碼
for(int i=0;i<100000;i++){
synchronized(this){
do();
}
// 被優化之後
synchronized(this){
for(int i=0;i<100000;i++){
do();
}
複製程式碼
輕量級鎖
引入輕量級鎖的主要目的是在沒有多執行緒競爭的前提下,減少傳統的重量級鎖使用作業系統互斥量產生的效能消耗。

只有在“對於絕大部分的鎖,在整個生命週期內都是不會存在競爭的”情況下,輕量級鎖才有較好的效能。

如果存在競爭的情況,輕量級鎖需要膨脹為重量級鎖,而且還會有額外的CAS操作,會比重量級鎖效能更差。

正常獲取鎖過程:

1)當前執行緒的棧幀中建立一個的空間Lock Record,將鎖物件的Mark Word的拷貝過來

2)JVM利用CAS操作將物件的Mark Word更新為指向Lock Record的指標,如果成功表示競爭到鎖

3)直接執行同步塊程式碼,不需要monitor

獲取鎖所有情況:

1)判斷當前物件是否處於無鎖狀態(hashcode、0、01)

無鎖狀態:JVM首先將在當前執行緒的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於儲存鎖物件目前的Mark Word的拷貝,執行(2)

有鎖:執行步驟(3);

2)JVM利用CAS操作嘗試將物件的Mark Word更新為指向Lock Record的指標

更新指標成功:表示競爭到鎖,則將鎖標誌位變成00(表示此物件處於輕量級鎖狀態),直接執行同步塊程式碼,不需要monitor,結束;

更新指標成功:未競爭到鎖,執行步驟(3);

3)判斷當前物件的Mark Word是否指向當前執行緒的棧幀,

指向當前執行緒的棧幀:表示當前執行緒已經持有當前物件的鎖,則直接執行同步程式碼塊,不需要monitor,結束;

不指向當前執行緒的棧幀:說明該鎖物件已經被其他執行緒搶佔了,這時輕量級鎖需要膨脹為重量級鎖,鎖標誌位變成10,後面等待的執行緒將會進入阻塞狀態;

釋放鎖 :

1)取出在獲取輕量級鎖儲存在Displaced Mark Word中的資料;

2)用CAS操作將取出的資料替換當前物件的Mark Word中,如果成功,則說明釋放鎖成功,否則執行(3);

3)如果CAS操作替換失敗,說明有其他執行緒嘗試獲取該鎖,則需要在釋放鎖的同時需要喚醒被掛起的執行緒。

偏向鎖
引入偏向鎖主要目的是:為了在無多執行緒競爭的情況下儘量減少不必要的輕量級鎖執行路徑。

獲取鎖

1)檢測Mark Word是否為可偏向狀態,即是否為偏向鎖1,鎖標識位為01;

2)若為可偏向狀態,則測試執行緒ID是否為當前執行緒ID,如果是,則執行步驟(5),否則執行步驟(3);

3)如果執行緒ID不為當前執行緒ID,則通過CAS操作競爭鎖,競爭成功,則將Mark Word的執行緒ID替換為當前執行緒ID,否則執行執行緒(4);

4)通過CAS競爭鎖失敗,證明當前存在多執行緒競爭情況,當到達全域性安全點,獲得偏向鎖的執行緒被掛起,偏向鎖升級為輕量級鎖,然後被阻塞在安全點的執行緒繼續往下執行同步程式碼塊;

5)執行同步程式碼塊

釋放鎖

偏向鎖的釋放採用了一種只有競爭才會釋放鎖的機制,執行緒是不會主動去釋放偏向鎖,需要等待其他執行緒來競爭。偏向鎖的撤銷需要等待全域性安全點(這個時間點是上沒有正在執行的程式碼)。其步驟如下:

1)暫停擁有偏向鎖的執行緒,判斷鎖物件石是否還處於被鎖定狀態;

2)撤銷偏向蘇,恢復到無鎖狀態(01)或者輕量級鎖的狀態;