線程安全是一個(gè)比較復(fù)雜的概念嫌吠。當(dāng)多個(gè)線程訪問某個(gè)類時(shí),不受運(yùn)行環(huán)境的調(diào)度方式和這些線程的交替執(zhí)行順序的影響掺炭,也不需要額外的同步,這個(gè)類都能表現(xiàn)出正確的行為凭戴,那么就認(rèn)為它是線程安全的涧狮。常見的并發(fā)編程要么是用得太少未能發(fā)揮計(jì)算能力,要么就是使用不當(dāng)么夫,帶來極大的風(fēng)險(xiǎn)者冤。
線程安全的兩個(gè)特性,原子性(atomicity)和?可見性(visibility)档痪。
原子性就是指對(duì)數(shù)據(jù)的操作是一個(gè)獨(dú)立的涉枫、不可分割的整體。如果一次操作對(duì)應(yīng)一條操作系統(tǒng)指令腐螟,這樣肯定可以能保證原子性愿汰。但是很多操作不能通過一條指令就完成困后。int++?就是一個(gè)典型的非原子操作 ,它對(duì)應(yīng)的是三個(gè)動(dòng)作:取值衬廷、在原值+1摇予、寫回。在并發(fā)環(huán)境下吗跋,往往在第二步就會(huì)遇到問題侧戴,原值已經(jīng)被其他線程修改,當(dāng)前線程仍舊將過時(shí)的“原值”+1后寫回跌宛,導(dǎo)致得到一個(gè)無法預(yù)期的結(jié)果酗宋。非原子操作都存在線程安全問題。要保證組合操作的原子性疆拘,有三種方式: synchronized 蜕猫、 Lock 、非阻塞算法(如CAS入问,比較并交換)丹锹。
要理解可見性,需要先對(duì)JVM的內(nèi)存模型有一定的了解芬失。簡(jiǎn)單來說楣黍,就是每個(gè)線程有私有的工作內(nèi)存,變量會(huì)從共享內(nèi)存拷貝過來棱烂,運(yùn)算動(dòng)作只能針對(duì)工作內(nèi)存中的變量副本進(jìn)行租漂,完成后再將結(jié)果刷回共享內(nèi)存。在并發(fā)環(huán)境下颊糜,如果多條線程同時(shí)操作一個(gè)共享變量的不同副本哩治,某條線程對(duì)變量副本的修改結(jié)果,需要一個(gè)機(jī)制來確保其他線程能“看見”(并刷新自己的變量副本)修改后的最新值衬鱼。
往下討論之前业筏,建議先了解以下兩個(gè)主題:
1、Java并發(fā)之一:Java內(nèi)存模型?
2鸟赫、Java并發(fā)之二:JVM視角下的volatile
為了滿足原子性蒜胖、可見性,Java提供了四種不同的實(shí)現(xiàn):volatile抛蚤、原子類(Atomic)台谢、同步(synchronized)、鎖岁经。
volatile是JVM提供的最輕量級(jí)的同步機(jī)制朋沮。volatile提供單個(gè)field的內(nèi)存同步控制,synchronized則提供整個(gè)臨界區(qū)(代碼塊/方法)的同步控制缀壤。volatile?確保了此變量對(duì)所有線程的實(shí)時(shí)可見性樊拓。普通變量做不到這一點(diǎn)纠亚,普通變量的值在線程間傳遞是異步的,需要通過主內(nèi)存來完成骑脱。但 volatile?只提供了讀寫操作的原子性菜枷,沒提供組合操作的原子性(如依賴原值的 int ++ )。正確地運(yùn)用 volatile?必須考慮它的適用場(chǎng)景:運(yùn)算結(jié)果并不依賴變量的當(dāng)前值叁丧,或者能夠確保只有單一線程修改變量的值啤誊。
原子變量類可以認(rèn)為是?volatile?變量的泛化,它主要通過volatile和硬件支持的CAS算法來支持原子條件的比較并設(shè)置更新拥娄。原子變量類的另一個(gè)更重要的用途是構(gòu)建高效的非阻塞算法蚊锹。非阻塞算法用底層的原子機(jī)器指令代替鎖來確保數(shù)據(jù)在并發(fā)訪問中的一致性。與基于鎖的方案相比稚瘾,非阻塞算法在設(shè)計(jì)和實(shí)現(xiàn)上要復(fù)雜得多牡昆,但它的可伸縮性和活躍性上擁有較大優(yōu)勢(shì)。
同步(synchronized)摊欠,每個(gè)對(duì)象或類有一個(gè)隱式鎖丢烘,每次進(jìn)入synchronized臨界區(qū)時(shí)獲取或阻塞等待該鎖,退出時(shí)釋放鎖些椒。隱式鎖是互斥鎖播瞳,每次只能有一個(gè)線程獲取,未釋放之前其他線程要么阻塞等待直到對(duì)方釋放免糕,要么放棄赢乓。早期的 synchronized 實(shí)現(xiàn)很重,性能不佳石窑。Java 5/6引入鎖升級(jí)的機(jī)制進(jìn)行了優(yōu)化牌芋。此外,synchronized?也有不少功能性的限制 —— 它無法中斷一個(gè)正在等候獲得鎖的線程松逊,也無法通過輪詢得到鎖躺屁。對(duì)此,Java在JUC包中引入了Lock?框架经宏。
Lock?框架?是同步的兼容替代品楼咳,提供了與synchronized關(guān)鍵字類似的同步功能。Lock在使用時(shí)需要顯式地獲取和釋放鎖烛恤。雖然缺少了隱式鎖的便捷性, 但是卻擁有了鎖獲取與釋放的可操作性余耽、 可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性缚柏,允許編程人員對(duì)鎖管理進(jìn)行更細(xì)致的控制。Lock接口的實(shí)現(xiàn)基本都是通過聚合了一個(gè)同步器的子類來完成線程訪問控制的碟贾。說到同步器币喧,就要去了解AQS(AbstractQueuedSychronizer)轨域,AQS與CAS構(gòu)成了JUC包的核心。仔細(xì)分析JUC包的源代碼實(shí)現(xiàn)杀餐,會(huì)發(fā)現(xiàn)一個(gè)通用的實(shí)現(xiàn)模式:
首先干发,聲明共享變量為volatile;
然后史翘,使用CAS的原子條件更新來實(shí)現(xiàn)線程之間的同步枉长;
同時(shí),利用volatile的讀/寫和CAS配合實(shí)現(xiàn)線程之間的通信琼讽。
AQS必峰、非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類,這些基礎(chǔ)類都是使用這種模式來實(shí)現(xiàn)的钻蹬,而高層類又是依賴于這些基礎(chǔ)類來實(shí)現(xiàn)的吼蚁。
以上這些內(nèi)容,再加上線程創(chuàng)建(Thread/Runnable/Callable)问欠、運(yùn)行管理(Executor框架)肝匆、容器(阻塞隊(duì)列、線程安全集合)等顺献,這就是Java并發(fā)機(jī)制的索驥圖了旗国。
參考:
1、《Java并發(fā)編程實(shí)戰(zhàn)》Brian Goetz等人
2滚澜、《Java思想與實(shí)踐》Brian Goetz?
3粗仓、JAVA CAS原理深度分析