Java線程安全總結(jié)

1、概念

進(jìn)程和線程都是一個(gè)時(shí)間段的描述急波,是CPU工作時(shí)間段的描述。兩者顆粒度不同瘪校。
進(jìn)程是CPU資源分配的最小單位澄暮,可以理解為一個(gè)應(yīng)用程序。
線程是CPU調(diào)度的最小單位阱扬,是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位泣懊。

2、三個(gè)核心

原子性

一個(gè)操作麻惶,要么全部執(zhí)行馍刮,要不么全部不執(zhí)行。
簡(jiǎn)單的說(shuō)窃蹋,就是在一個(gè)線程對(duì)共享變量進(jìn)行操作時(shí)卡啰,阻塞其他線程對(duì)該變量的操作。

可見(jiàn)性

當(dāng)線程操作某個(gè)變量時(shí)警没,順位為:
1匈辱、將變量從主內(nèi)存拷貝到工作內(nèi)存中。
2杀迹、執(zhí)行代碼亡脸,操作共享變量值。
3树酪、將工作內(nèi)存的數(shù)據(jù)刷新到主內(nèi)存中浅碾。
多個(gè)線程并發(fā)訪問(wèn)共享變量時(shí),一個(gè)線程對(duì)共享變量的操作嗅回,其他線程能夠立刻看到及穗。
每個(gè)線程讀取共享變量時(shí),都會(huì)將該變量加載進(jìn)其對(duì)應(yīng)CPU的高速緩存里绵载,修改該變量后埂陆,CPU會(huì)立即更新該緩存苛白,但并不一定會(huì)立即將其寫(xiě)回主內(nèi)存(實(shí)際上寫(xiě)回主內(nèi)存的時(shí)間不可預(yù)期)。此時(shí)其它線程(尤其是不在同一個(gè)CPU上執(zhí)行的線程)訪問(wèn)該變量時(shí)焚虱,從主內(nèi)存中讀到的就是舊的數(shù)據(jù)购裙,而非第一個(gè)線程更新后的數(shù)據(jù)。

順序性

程序的執(zhí)行順序按照代碼的先后順序執(zhí)行鹃栽。

int a,b;
a++;
b++;
if(b==1){
  print(a);
}

在理想情況下躏率,當(dāng)b=1時(shí),a=1,但是實(shí)際情況中民鼓,JVM在執(zhí)行代碼的過(guò)程中薇芝,并不一定按照代碼的順序執(zhí)行,有可能先執(zhí)行b++,后執(zhí)行a++丰嘉。
happens-before 原則
1夯到、程序次序規(guī)則:一個(gè)線程內(nèi),按照代碼順序饮亏,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作耍贾。
2、鎖定規(guī)則:一個(gè)unlock操作一定發(fā)生在lock操作之前路幸。
3荐开、volatile變量規(guī)則:對(duì)一個(gè)變量的寫(xiě)操作先行發(fā)生于后面的讀操作。
4简肴、傳遞規(guī)則:如果操作A先行發(fā)生于操作B晃听,而操作B又先行發(fā)生于操作C,則可以得出操作A先行發(fā)生于操作C着帽。
5杂伟、線程啟動(dòng)規(guī)則:Thread對(duì)象的所有操作都發(fā)生在start()之后。
6仍翰、線程中斷規(guī)則:線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生。
7观话、線程終結(jié)規(guī)則:線程中所有的操作都先行發(fā)生于線程的終止檢測(cè)予借。
8、對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成先行發(fā)生于他的finalize()方法的開(kāi)始频蛔。

對(duì)于程序次序規(guī)則灵迫,應(yīng)該理解為jvm保證最終執(zhí)行的結(jié)果與程序順序執(zhí)行的結(jié)果一致。jvm有可能對(duì)不存在數(shù)據(jù)依賴性的指令進(jìn)行重排序晦溪。實(shí)際上瀑粥,這個(gè)規(guī)則是用來(lái)保證單線程中執(zhí)行結(jié)果的正確性,但無(wú)法保證程序在多線程中執(zhí)行的正確性三圆。

3狞换、線程狀態(tài)

  • INIT : 線程對(duì)象進(jìn)行new初始化后避咆,此時(shí)還未調(diào)用start()。
  • NEW : 線程對(duì)象調(diào)用start()方法后修噪,進(jìn)去可運(yùn)行狀態(tài)查库。如果處于RUNABLED狀態(tài)的線程調(diào)用yield()后,會(huì)釋放占用的資源黄琼,重新進(jìn)入NEW狀態(tài)樊销。
  • RUNABLED : 線程獲取到CPU時(shí)間片,進(jìn)入運(yùn)行狀態(tài)脏款。
  • BLOCKED : 線程調(diào)用sleep()或者join()方法后围苫,進(jìn)去阻塞狀態(tài),此時(shí)線程不釋放所占有的系統(tǒng)資源撤师。當(dāng)sleep()結(jié)束或者join()等到其他線程到來(lái)够吩,當(dāng)前線程進(jìn)入RUNABLED狀態(tài)。
  • TIME WAITING : 線程進(jìn)入到RUNABLED狀態(tài)丈氓,還未開(kāi)始運(yùn)行的時(shí)候周循,發(fā)現(xiàn)要獲取的資源處于同步狀態(tài),該線程就會(huì)進(jìn)入TIME WAITING狀態(tài)万俗,等待資源釋放湾笛;當(dāng)前線程使用wait()方法后,進(jìn)入TIME WAITING狀態(tài)闰歪,只有在獲得notify()或者notifyAll()通知后嚎研,才會(huì)進(jìn)入WAITING狀態(tài)。

4库倘、關(guān)鍵字

synchronized

當(dāng)synchronized修飾一個(gè)方法或者代碼塊的時(shí)候临扮,保證同時(shí)只有一個(gè)線程可以訪問(wèn)該方法或代碼塊。保證了線程的執(zhí)行順序性和可見(jiàn)性教翩。

synchronized(鎖){
     臨界區(qū)代碼
}
public void synchronized method(){
    方法體
}

synchronized修飾代碼塊杆勇,鎖就是這個(gè)對(duì)象;
synchronized修飾方法饱亿,鎖就是這個(gè)class蚜退。
理論上所有對(duì)象都可以成為鎖,但是能被多個(gè)線程共享的鎖才有意義彪笼。
每個(gè)鎖對(duì)象有兩個(gè)隊(duì)列钻注,一個(gè)是就緒隊(duì)列,一個(gè)是阻塞隊(duì)列配猫。就緒隊(duì)列存儲(chǔ)了即將獲取鎖的線程幅恋,阻塞隊(duì)列存儲(chǔ)被阻塞的線程。
java內(nèi)置鎖是可重入鎖泵肄,子類(lèi)可以獲得父類(lèi)的鎖資源捆交。
synchronized是一種悲觀鎖淑翼,會(huì)導(dǎo)致其他所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖零渐。這樣的鎖對(duì)性能不夠友好窒舟。

volatile

volatile關(guān)鍵字可以保證可見(jiàn)性,當(dāng)使用volatile來(lái)修飾某個(gè)共享變量時(shí)诵盼,會(huì)保證該變量的修改會(huì)立刻更新到主內(nèi)存中惠豺,并且將其他緩存中對(duì)該變量的緩存設(shè)置為無(wú)效,其他線程需要重新從主內(nèi)存讀取該變量风宁。
volatile關(guān)鍵字可以禁止進(jìn)行指令重排序洁墙。
單線程下

x = 1; //語(yǔ)句1
y=0;  //語(yǔ)句2
volatile flag = true; //語(yǔ)句3
x = 2; //語(yǔ)句4
y = 4; //語(yǔ)句5

使用volatile修飾flag后,jvm在進(jìn)行指令重排序時(shí)戒财,不會(huì)將語(yǔ)句4热监,5放在語(yǔ)句3之前,也不會(huì)將語(yǔ)句1饮寞,2放在語(yǔ)句3之后孝扛。
多線程下

//線程1
object = loadObject(); //語(yǔ)句1
init = true; //語(yǔ)句2
//線程2
while(!init){
  ...
}
doSomething(object);

在多線程的情況下,線程1有可能先執(zhí)行語(yǔ)句2幽崩,假如此時(shí)線程1進(jìn)入阻塞苦始,線程2開(kāi)始執(zhí)行,但此時(shí)語(yǔ)句1還沒(méi)執(zhí)行慌申,obejct沒(méi)有被初始化陌选,導(dǎo)致程序出錯(cuò)。
這里用volatile修飾init蹄溉,可以保證語(yǔ)句1先執(zhí)行咨油。
原理
加入volatile關(guān)鍵字時(shí),會(huì)多出一個(gè)lock前綴指令柒爵。
lock前綴指令相當(dāng)于一個(gè)內(nèi)存屏障役电,有3個(gè)功能:
(1)、確保指令重排序時(shí)不會(huì)把內(nèi)存屏障之后的指令排在內(nèi)存屏障之前餐弱,也不會(huì)把內(nèi)存屏障之前的指令排在內(nèi)存屏障之后宴霸。
(2)、強(qiáng)制將對(duì)緩存的修改操作立即寫(xiě)入主內(nèi)存膏蚓。
(3)、如果是寫(xiě)操作畸写,會(huì)導(dǎo)致其他CPU中對(duì)應(yīng)的緩存行無(wú)效驮瞧。

Lock

java.util.concurrent.lock中的lock框架是鎖定的一個(gè)抽象,它允許把鎖定的實(shí)現(xiàn)作為java類(lèi)枯芬。它擁有與synchronized相同的并發(fā)性和內(nèi)存語(yǔ)義论笔,但是添加了類(lèi)似鎖投票采郎、定時(shí)鎖等候和中斷鎖等候的一些特性。此外狂魔,它在激烈爭(zhēng)用的情況下具有更加的性能蒜埋。
不要忘了在finally中釋放lock

讀寫(xiě)鎖

ReentrantReadWriteLock

悲觀鎖和樂(lè)觀鎖

共享鎖和排他鎖

CAS

Compare And Swape,比較并交換最楷。目前CAS被廣泛應(yīng)用于硬件層面的并發(fā)操作整份。
樂(lè)觀鎖的機(jī)制就是CAS,樂(lè)觀鎖就是每次不加鎖籽孙,假設(shè)沒(méi)有沖突的去完成某項(xiàng)操作烈评,如果因?yàn)闆_突失敗就重試,直到成功為止犯建。
CAS操作包含三個(gè)操作數(shù)--內(nèi)存位置V讲冠,預(yù)期原值A(chǔ)和新值B。如果內(nèi)存位置的值與預(yù)期原值匹配适瓦,那么將該位置替換為新值竿开。否則,處理器不作任何處理玻熙。
利用CPU的CAS指令否彩,同時(shí)借助JNI來(lái)完成JAVA的非阻塞算法。其他原子操作都是利用類(lèi)似的特性完成的揭芍。而整個(gè)JUC都是建立在CAS的基礎(chǔ)上的胳搞。
缺點(diǎn)
CAS雖然具有很高效的原子操作,但是CAS仍然存在三大問(wèn)題称杨。
(1)肌毅、ABA問(wèn)題。如果一個(gè)值原來(lái)是A姑原,變成了B悬而,又變成了A,那么使用CAS檢查時(shí)會(huì)認(rèn)為它的值沒(méi)有發(fā)生變化锭汛,但是實(shí)際上發(fā)生了變化笨奠。解決思路就是加版本號(hào),1A-2B-3A唤殴。
(2)般婆、循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大。CAS是自旋鎖朵逝,如果長(zhǎng)時(shí)間不成功蔚袍,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷(xiāo)。
(3)、只能保證一個(gè)共享變量的原子操作啤咽。

Double-Check

在單例模式的懶漢式中晋辆,存在雙重檢查,這種方式在多線程中是不安全的宇整。

public Singleton getResource(){
    if (resource == null){   //語(yǔ)句1
        synchronized(this){
            if (resource == null) {  //語(yǔ)句2
                  resource = new Singleton(); //語(yǔ)句3
            }
        }
    }
    return resource;
}

假設(shè)線程1執(zhí)行到了語(yǔ)句1瓶佳,執(zhí)行了new Resource()指令,但是還未給resource賦值鳞青,此時(shí)線程1阻塞霸饲,線程2開(kāi)始執(zhí)行,在判斷resource == null是盼玄,因?yàn)橐呀?jīng)分配了內(nèi)存空間(未賦值)贴彼,該語(yǔ)句為false,就返回了未完成初始化的resource埃儿,造成程序錯(cuò)誤器仗。
改進(jìn)方法
(1)、在方法上加synchronized童番。

public synchronized Singleton getResource(){
    if (resource == null){  
          resource = new Singleton(); 
    }
    return resource;
}

(2)精钮、使用volatile

private valotile Singleton resource = null;
public synchronized Singleton getResource(){
    if (resource == null){  
          resource = new Singleton(); 
    }
    return resource;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市剃斧,隨后出現(xiàn)的幾起案子轨香,更是在濱河造成了極大的恐慌,老刑警劉巖幼东,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臂容,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡根蟹,警方通過(guò)查閱死者的電腦和手機(jī)脓杉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)简逮,“玉大人球散,你說(shuō)我怎么就攤上這事∩⑹” “怎么了蕉堰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)悲龟。 經(jīng)常有香客問(wèn)我屋讶,道長(zhǎng),這世上最難降的妖魔是什么须教? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任丑婿,我火速辦了婚禮,結(jié)果婚禮上没卸,老公的妹妹穿的比我還像新娘羹奉。我一直安慰自己,他們只是感情好约计,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布诀拭。 她就那樣靜靜地躺著,像睡著了一般煤蚌。 火紅的嫁衣襯著肌膚如雪耕挨。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天尉桩,我揣著相機(jī)與錄音筒占,去河邊找鬼。 笑死蜘犁,一個(gè)胖子當(dāng)著我的面吹牛翰苫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播这橙,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奏窑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了屈扎?” 一聲冷哼從身側(cè)響起埃唯,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹰晨,沒(méi)想到半個(gè)月后墨叛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡模蜡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年漠趁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哩牍。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棚潦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出膝昆,到底是詐尸還是另有隱情丸边,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布荚孵,位于F島的核電站妹窖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏收叶。R本人自食惡果不足惜骄呼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜓萄,春花似錦隅茎、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至绸硕,卻和暖如春堂竟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玻佩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工出嘹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咬崔。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓税稼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親刁赦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娶聘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司甚脉,掛了不少丸升,但最終還是拿到小米、百度牺氨、阿里狡耻、京東、新浪猴凹、CVTE夷狰、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,243評(píng)論 11 349
  • Java8張圖 11、字符串不變性 12郊霎、equals()方法沼头、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,702評(píng)論 0 11
  • 淺談java內(nèi)存模型 不同的平臺(tái)书劝,內(nèi)存模型是不一樣的进倍,但是jvm的內(nèi)存模型規(guī)范是統(tǒng)一的。其實(shí)java的多線程并發(fā)問(wèn)...
    流浪java閱讀 388評(píng)論 1 3
  • 一购对、多線程 說(shuō)明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)猾昆。 NEW:這種情況指的是,通過(guò) New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,676評(píng)論 0 44
  • 夏雨溫溫似玉鉤骡苞, 叩我閑窗憶舊游垂蜗。 星散南海十年路楷扬, 月映長(zhǎng)江千里流。 清晨寂寂鸚鵡院贴见, 午后喧喧佛牙樓烘苹。 路遙酒...
    南風(fēng)小帥閱讀 323評(píng)論 1 2