鎖是并發(fā)編程中經(jīng)常用到的,本文主要分析下synchronized和lock鎖機(jī)制的區(qū)別。
性能區(qū)別
分兩種場景來比較,競爭不激烈和競爭激烈情況
競爭不激烈
private static final int THREAD_NUM = 10000;
private static int test = 0;
private static ThreadPoolExecutor pool =(ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_NUM);
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_NUM; i++) {
pool.execute(TestReentrantLock::testLock);
pool.execute(TestReentrantLock::testSynchronized);
}
pool.shutdown();
while(pool.getActiveCount()!=0) {}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
private static void testLock() {
try {
lock.lock();
test++;
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
lock.unlock();
}
}
private static synchronized void testSynchronized() {
test++;
}
上面代碼都是簡單的實(shí)現(xiàn),開啟10000個線程對一個變量++操作,競爭不激烈,結(jié)果顯示都在1000左右,不分上下
競爭激烈
public class TestReentrantLock {
private static final int THREAD_NUM = 10;
private static double test = 0;
private static ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(THREAD_NUM);
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_NUM; i++) {
pool.execute(TestReentrantLock::testLock);
//pool.execute(TestReentrantLock::testSynchronized);
}
pool.shutdown();
while (pool.getActiveCount() != 0) {
}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
private static void testLock() {
for (int i = 0; i < 1000; i++) {
lock.lock();
try {
Thread.sleep(2);
test++;
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
lock.unlock();
}
}
}
private static void testSynchronized() {
for (int i = 0; i < 1000; i++) {
synchronized (lock) {
try {
Thread.sleep(2);
test++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
構(gòu)建競爭激烈場景,Synchronized性能和lock的性能也差不多
自從synchronized優(yōu)化之后,總之不管競爭激烈還是不激烈,Synchronized和lock的性能都差不多。
原理介紹
synchronized
JDK1.5中,synchronized是性能低效的。因?yàn)檫@是一個重量級操作,它對性能最大的影響是阻塞的是實(shí)現(xiàn),掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性帶來了很大的壓力。到了JDK1.6,對synchronized加入了很多優(yōu)化措施,有自適應(yīng)自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。
要想理解synchronized實(shí)現(xiàn),首先要知道java對象的數(shù)據(jù)結(jié)構(gòu)
java對象數(shù)據(jù)結(jié)構(gòu)
對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對齊填充(Padding)。
其中對象頭主要包括兩部分:markWord和klass
markWord數(shù)據(jù)結(jié)構(gòu)如下:
hash: 保存對象的哈希碼
age: 保存對象的分代年齡
biased_lock: 偏向鎖標(biāo)識位
lock: 鎖狀態(tài)標(biāo)識位
JavaThread:* 保存持有偏向鎖的線程ID
epoch: 保存偏向時間戳
對象頭的另外一部分是klass類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個指針來確定這個對象是哪個類的實(shí)例。
其中鎖狀態(tài)標(biāo)識位標(biāo)識了重量級鎖、輕量級鎖和偏向鎖狀態(tài),內(nèi)容如下:

偏向鎖
偏向鎖,算是競爭鎖資源的第一層緩沖,一般指向第一個訪問鎖線程,如果沒有競爭,對象頭一直處于偏向鎖狀態(tài),當(dāng)只有發(fā)生鎖資源競爭的時候偏向鎖才會撤銷,升級成輕量級鎖,或者無鎖狀態(tài)
偏向鎖獲取過程:
第一步,首先訪問Mark Word中偏向鎖的標(biāo)識是否設(shè)置成1,鎖標(biāo)志位是否為01,確認(rèn)是否是偏向鎖。
第二步,如果為可偏向狀態(tài),則測試線程ID是否指向當(dāng)前線程,如果是,進(jìn)入步驟5,否則進(jìn)入步驟3。
第三步,如果線程ID并未指向當(dāng)前線程,則通過CAS操作競爭鎖。如果競爭成功,則將Mark Word中線程ID設(shè)置為當(dāng)前線程ID,然后執(zhí)行5;如果競爭失敗,執(zhí)行4。
第四步,如果CAS獲取偏向鎖失敗,則表示有競爭。當(dāng)?shù)竭_(dá)全局安全點(diǎn)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。(撤銷偏向鎖的時候會導(dǎo)致stop the word)
第五步,執(zhí)行同步代碼。
偏向鎖釋放過程:
偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處于被鎖定狀態(tài),撤銷偏向鎖后恢復(fù)到未鎖定(標(biāo)志位為“01”)或輕量級鎖(標(biāo)志位為“00”)的狀態(tài)。
輕量級鎖
輕量級鎖是偏向鎖升級來的,即線程發(fā)生競爭,偏向鎖會升級成輕量級鎖
輕量級鎖獲取過程:
第一步,在線程進(jìn)入同步代碼塊中的時候,虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個名為鎖記錄的空間用于存儲對象的Mark Word。
第二步,把對象的Mark Word復(fù)制到該鎖記錄中。
第三步,虛擬機(jī)將使用CAS操作嘗試將對象的Mark Word中的內(nèi)容更新為指向鎖記錄的指針,成功執(zhí)行步驟4,否則執(zhí)行步驟5。
第四步,更新成功,那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標(biāo)志位設(shè)置為“00”,即表示此對象處于輕量級鎖定狀態(tài)。
第五步,更新失敗,虛擬機(jī)首先會檢查對象的Mark Word是否指向當(dāng)前線程的棧幀,如果是就說明當(dāng)前線程已經(jīng)擁有了這個對象的鎖,那就可以直接進(jìn)入同步塊。否則說明多個線程競爭鎖,輕量級鎖升級為重量級鎖,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”。這里升級重量級鎖之前,有一個自旋的過程,自旋成功獲取鎖就不需要升級到重量級鎖,失敗升級重量級鎖。
輕量級鎖釋放過程:
輕量級鎖在釋放鎖的時候如果它發(fā)現(xiàn)在它持有鎖的期間有其他線程來嘗試獲取鎖了,即獲取過程中第二步對markword做了修改,兩者比對發(fā)現(xiàn)不一致,則切換到重量鎖。
重量級鎖
Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)。
總結(jié):
synchronized·就是一個鎖升級的過程,偏向鎖->輕量級鎖(自旋)->重量級鎖,不能降級。
Lock實(shí)現(xiàn)
Lock是基于AQS實(shí)現(xiàn)的,可以參考ReentrantLock源碼解析
Lock vs synchronized
而lock的優(yōu)勢在于
1,更自由,不限制于鎖加于類,方法上,可以跨方法持有鎖
2,支持輪詢鎖,定時鎖,可中斷鎖等
synchronized的優(yōu)勢在于
1,用法簡單直接,原語關(guān)鍵字
2,不會產(chǎn)生gc垃圾。