From:Java并發(fā)編程的藝術(shù)
- 目錄
BiBi - 并發(fā)編程 -0- 開篇
BiBi - 并發(fā)編程 -1- 挑戰(zhàn)
BiBi - 并發(fā)編程 -2- volatile
BiBi - 并發(fā)編程 -3- 鎖
BiBi - 并發(fā)編程 -4- 原子操作
BiBi - 并發(fā)編程 -5- Java內(nèi)存模型
BiBi - 并發(fā)編程 -6- final關(guān)鍵字
BiBi - 并發(fā)編程 -7- DCL
BiBi - 并發(fā)編程 -8- 線程
BiBi - 并發(fā)編程 -9- ReentrantLock
BiBi - 并發(fā)編程 -10- 隊(duì)列同步器
BiBi - 并發(fā)編程 -11- 并發(fā)容器
BiBi - 并發(fā)編程 -12- Fork/Join框架
BiBi - 并發(fā)編程 -13- 并發(fā)工具類
BiBi - 并發(fā)編程 -14- 線程池
BiBi - 并發(fā)編程 -15- Executor框架
1. synchronized簡(jiǎn)介
synchronized內(nèi)存語義
當(dāng)線程釋放鎖時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存中件甥。
當(dāng)線程獲取鎖時(shí)漠嵌,JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無效,從主內(nèi)存中讀取概行。
synchronized在jvm中由monitorenter和monitorexit指令實(shí)現(xiàn)蠢挡,當(dāng)線程執(zhí)行到monitorenter指令時(shí),會(huì)去獲取所需要鎖凳忙。synchronized用的鎖存在Java對(duì)象頭里的Mark Word中业踏。
Java對(duì)象頭Mark Word的組成:鎖狀態(tài) + 對(duì)象的hashCode + 分代年齡
2. 不同級(jí)別的鎖
鎖的級(jí)別:偏向鎖 < 輕量級(jí)鎖 < 重量級(jí)鎖【只能升級(jí)不能降級(jí)】
偏向鎖 【同一線程中使用】
背景:大多數(shù)情況下,鎖不僅不存在競(jìng)爭(zhēng)涧卵,而且總是由同一線程多次獲得勤家。
特點(diǎn):在無競(jìng)爭(zhēng)情況下把整個(gè)同步都消除掉,連CAS操作都不做了柳恐。
實(shí)現(xiàn):當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí)伐脖,會(huì)在對(duì)象頭和棧幀中的鎖記錄里使用CAS操作存儲(chǔ)偏向鎖的線程ID,以后該線程進(jìn)入和退出同步塊時(shí)不需要進(jìn)行CAS操作來加鎖和解鎖乐设,只需要測(cè)試一下對(duì)象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖讼庇。如果成功,表示線程已經(jīng)獲取了該鎖近尚;如果測(cè)試失敗蠕啄,則需要再測(cè)試一下Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1:如果沒有設(shè)置,則使用CAS競(jìng)爭(zhēng)鎖戈锻;如果設(shè)置了介汹,則嘗試使用CAS將對(duì)象頭的偏向鎖指向當(dāng)前進(jìn)程。
撤銷:只有當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí)舶沛,持有偏向鎖的線程才會(huì)【在全局安全點(diǎn)嘹承,這個(gè)時(shí)間點(diǎn)不會(huì)執(zhí)行任何字節(jié)碼】釋放。如果持有鎖對(duì)象的線程已經(jīng)結(jié)束如庭,則將鎖對(duì)象恢復(fù)到未鎖定狀態(tài)叹卷;如果持有鎖對(duì)象的線程仍然活著撼港,則將鎖對(duì)象升級(jí)為輕量鎖。
提示:Java6默認(rèn)開啟偏向鎖骤竹,如果你的程序所有的鎖通常都會(huì)處于競(jìng)爭(zhēng)狀態(tài)帝牡,最好關(guān)閉偏向鎖,讓程序默認(rèn)進(jìn)入輕量級(jí)鎖狀態(tài)蒙揣。
輕量級(jí)鎖【自旋不阻塞】
- 自旋
背景:阻塞是互斥同步對(duì)性能最大的影響靶溜,掛起線程和恢復(fù)線程的操作需要轉(zhuǎn)入到內(nèi)核態(tài)中完成,這樣很影響性能懒震。
實(shí)現(xiàn):線程A持有鎖罩息,而線程B要競(jìng)爭(zhēng)該鎖。通常線程A會(huì)執(zhí)行很短一段時(shí)間就會(huì)釋放鎖个扰,為了這段時(shí)間讓線程B去掛起阻塞是不值得瓷炮。此時(shí)可以讓線程B進(jìn)入自旋(死循環(huán))狀態(tài),等線程A釋放鎖后递宅,線程B直接獲取娘香。【前提是在多處理器情況下办龄,線程A和B占用不同的處理器烘绽,一直在消耗資源。阻塞不會(huì)消耗資源俐填,但性能差】
輕量級(jí)鎖的實(shí)現(xiàn):線程在執(zhí)行同步塊之前安接,JVM現(xiàn)在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中玷禽,稱為Dispatch Mark Word。然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針呀打。如果成功矢赁,當(dāng)前線程獲得鎖,如果失敗贬丛,表示存在其他線程的競(jìng)爭(zhēng)撩银,當(dāng)前線程嘗試使用自旋來獲取鎖。
3. 不同鎖的使用場(chǎng)景
重量級(jí)鎖:線程競(jìng)爭(zhēng)不會(huì)自旋豺憔,不會(huì)消耗CUP额获,但線程會(huì)阻塞,響應(yīng)時(shí)間慢【適用于追求吞吐量恭应,同步塊執(zhí)行時(shí)間較長(zhǎng)的場(chǎng)景】
輕量級(jí)鎖:競(jìng)爭(zhēng)的線程不會(huì)阻塞抄邀,響應(yīng)快,但得不到鎖競(jìng)爭(zhēng)的線程使用自旋消耗CUP【適用于追求響應(yīng)時(shí)間昼榛,同步塊執(zhí)行時(shí)間較少的場(chǎng)景】
偏向鎖:加鎖和解鎖不需要額外的消耗境肾,但如果存在競(jìng)爭(zhēng)會(huì)帶來額外的鎖撤銷消耗【適用于只有一個(gè)線程,競(jìng)爭(zhēng)少的場(chǎng)景】
4. 鎖優(yōu)化
鎖消除
public void fun(){
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
}
變量sb的作用域在方法fun中,不會(huì)“逃逸”奥喻,其它線程無法訪問到偶宫,所以編譯器會(huì)消除append()方法的同步處理。
鎖粗化
對(duì)于零碎使用同一個(gè)鎖的操作环鲤,編譯器會(huì)把鎖同步的范圍進(jìn)行擴(kuò)展纯趋。
5. 悲觀/樂觀鎖
悲觀鎖
總是假設(shè)最壞的情況,每次取數(shù)據(jù)時(shí)都認(rèn)為其他線程會(huì)修改冷离,所以都會(huì)加鎖左驾。一旦加鎖倡鲸,不同線程同時(shí)執(zhí)行時(shí),只能有一個(gè)線程執(zhí)行,其他的線程在入口處等待甫菠,直到鎖被釋放。
樂觀鎖
樂觀鎖顧名思義就是在操作時(shí)很樂觀解幼,認(rèn)為操作不會(huì)產(chǎn)生并發(fā)問題(不會(huì)有其他線程對(duì)數(shù)據(jù)進(jìn)行修改)外永,因此不會(huì)上鎖。但是在更新時(shí)會(huì)判斷其他線程在這之前有沒有對(duì)數(shù)據(jù)進(jìn)行修改匙铡,一般會(huì)使用版本號(hào)機(jī)制或CAS算法實(shí)現(xiàn)图甜。