摘要:不可變?cè)谥校豢勺兊膶?duì)象一定是線程安全的。在里標(biāo)注自己是線程安全的類(lèi),大多都不是絕對(duì)線程安全,比如某些情況下類(lèi)在調(diào)用端也需要額外的同步措施。無(wú)同步方案要保證線程安全,不一定就得需要數(shù)據(jù)的同步,兩者沒(méi)有因果關(guān)系。
在之前學(xué)習(xí)編程的時(shí)候,有一個(gè)概念根深蒂固,即程序=算法+數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)代表問(wèn)題空間中的客體,代碼就用來(lái)處理這些數(shù)據(jù),這種思維是站在計(jì)算機(jī)的角度去抽象問(wèn)題和解決問(wèn)題,稱(chēng)之為面向過(guò)程編程。后來(lái)逐漸的發(fā)展,誕生了面向?qū)ο蟮木幊趟枷搿C嫦驅(qū)ο笫钦驹诂F(xiàn)實(shí)世界的角度去抽象解決問(wèn)題,把數(shù)據(jù)和行為都看成對(duì)象的一部分。
有了面向?qū)ο蟮木幊棠J剑瑯O大的地提升了現(xiàn)代軟件的開(kāi)發(fā)效率和規(guī)模,但是現(xiàn)實(shí)世界和計(jì)算機(jī)世界還是有很大的差異。比如人們很難想象在現(xiàn)實(shí)世界中進(jìn)行一項(xiàng)工作的時(shí)候,不停的中斷和切換,某些屬性也會(huì)在中斷期間改變,而這些事件在計(jì)算機(jī)里是很正常的。因此不得不妥協(xié),首先在保證數(shù)據(jù)的準(zhǔn)確性之后,才能來(lái)談高效。
1 什么叫線程安全我們談?wù)摰木€程安全,是限定在多個(gè)線程之間存在共享數(shù)據(jù)訪問(wèn),因?yàn)槿绻欢未a根本不會(huì)與其他線程共享數(shù)據(jù),那也就不會(huì)出現(xiàn)線程安全問(wèn)題。
當(dāng)多個(gè)線程訪問(wèn)一個(gè)對(duì)象的時(shí)候,如果不考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他協(xié)調(diào)操作的時(shí)候,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果,那這個(gè)對(duì)象就是線程安全的。
也就是當(dāng)一個(gè)對(duì)象可以安全的被多個(gè)線程同時(shí)使用,那么它就是線程安全對(duì)象。
2 java線程安全按照線程安全的安全程度來(lái)分的話,java中的各種操作共享的數(shù)據(jù)主要分為5類(lèi):不可變、絕對(duì)線程安全、相對(duì)線程安全、線程兼容和線程對(duì)立。
2.1 不可變在java中,不可變(immutable)的對(duì)象一定是線程安全的。
對(duì)于final關(guān)鍵字可見(jiàn)性來(lái)說(shuō),只要一個(gè)不可變對(duì)象被正確構(gòu)建出來(lái),那其外部的可見(jiàn)狀態(tài)永遠(yuǎn)也不會(huì)改變。不可變帶來(lái)的安全性是最簡(jiǎn)單和最純粹的。
對(duì)于基本數(shù)據(jù)類(lèi)型來(lái)說(shuō),只需要用final修飾即可。
對(duì)于對(duì)象來(lái)說(shuō),將對(duì)象中帶有狀態(tài)的變量都設(shè)置為final。
在java api中復(fù)核不可變要求的類(lèi)型主要有:
String
枚舉類(lèi)型
Long和Double等數(shù)值包裝類(lèi)型
BigInteger和BigDecimal等大數(shù)據(jù)類(lèi)型
AtomicLong和AtomInteger等原子類(lèi)并非是不可變的類(lèi)型。
2.2 絕對(duì)線程安全一個(gè)類(lèi)要達(dá)到“不管運(yùn)行時(shí)環(huán)境如何,調(diào)用者都不需要任何額外的同步措施”這個(gè)條件,通常是需要付出很大,甚至是有些不切實(shí)際的代價(jià)。
在java里標(biāo)注自己是線程安全的類(lèi),大多都不是絕對(duì)線程安全,比如某些情況下Vector類(lèi)在調(diào)用端也需要額外的同步措施。
2.3 相對(duì)線程安全這個(gè)就是我們通常意義所說(shuō)的線程安全。
它需要保證對(duì)這個(gè)對(duì)象多帶帶的操作時(shí)線程安全的,我們?cè)谡{(diào)用的時(shí)候不需要做額外的保障措施。但是對(duì)于一些特定順序的連續(xù)調(diào)用,就可能需要在調(diào)用端使用額外的同步手段來(lái)保證調(diào)用的正確性。
如vector,hashtable等線程安全類(lèi)都是屬于這種的。
2.4 線程兼容線程兼容是指對(duì)象本身并不是線程安全的,但是可以通過(guò)在調(diào)用端正確使用同步手段來(lái)保證對(duì)象在并發(fā)環(huán)境中可以安全使用。
java中的ArrayList和HashMap就是這種。
2.5 線程對(duì)立指無(wú)論調(diào)用端是否采用同步措施,都無(wú)法在多線程環(huán)境中并發(fā)使用代碼。
一個(gè)例子就是Thread類(lèi)的suspen方法和resume方法,如果有兩個(gè)線程同時(shí)持有一個(gè)線程對(duì)象,一個(gè)去中斷線程,一個(gè)去恢復(fù)線程,如果并發(fā)進(jìn)行的話,無(wú)論調(diào)用是否采用了同步,都會(huì)存在鎖死的風(fēng)險(xiǎn)。
常見(jiàn)的線程對(duì)立例子還有:
System.setIn()
System.setOut()
System.runFinalizersOnExit()
3 線程安全的實(shí)現(xiàn)方法如何實(shí)現(xiàn)線程安全與代碼編寫(xiě)有著很大的關(guān)系,但是虛擬機(jī)提供的同步和鎖的機(jī)制也起到了非常重要的作用。
3.1 互斥同步互斥同步是一種比較常見(jiàn)的并發(fā)正確性保障手段。
同步是指在多個(gè)線程并發(fā)訪問(wèn)共享數(shù)據(jù)時(shí),保證共享數(shù)據(jù)在同一時(shí)刻只被一個(gè)(或是一些,使用信號(hào)量的時(shí)候是一些)線程使用。互斥是實(shí)現(xiàn)同步的一種手段,臨界區(qū)、互斥量和信號(hào)量等都是主要的互斥手段。
synchronized
java中最基本的互斥同步手段就是synchronized關(guān)鍵字。
synchronized關(guān)鍵字在經(jīng)過(guò)編譯之后,會(huì)在同步塊的前后形成monitorenter和monitorexit這兩個(gè)字節(jié)碼指令。這兩個(gè)字節(jié)碼都需要一個(gè)引用類(lèi)型的參數(shù)來(lái)指明鎖定和解鎖的對(duì)象。如果在java程序中指明了這個(gè)對(duì)象,那么這個(gè)參數(shù)就是此對(duì)象的引用,如果沒(méi)有指定,那就根據(jù)synchronized修飾的是實(shí)例方法還是類(lèi)方法來(lái)取對(duì)應(yīng)的對(duì)象實(shí)例或者Class對(duì)象來(lái)作為鎖對(duì)象。
synchronized同步塊對(duì)同一條線程來(lái)說(shuō)是可重入的,不會(huì)出現(xiàn)自己把自己鎖死的問(wèn)題。
synchronized同步塊在已進(jìn)入的線程執(zhí)行完之前,會(huì)阻塞后面其他線程的進(jìn)入。
ReentrantLock
java.util.concurrent包中提供了重入鎖來(lái)實(shí)現(xiàn)同步。
ReentrantLock在寫(xiě)的時(shí)候,使用lock()和unlock()方法配合try/finally來(lái)完成,相比synchronized增加了一些高級(jí)功能:
等待可中斷
當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖的時(shí)候,正在等待的線程可以放棄等待,改為處理別的事情。
可實(shí)現(xiàn)公平鎖
公平鎖是指多個(gè)線程在等待一個(gè)同一個(gè)鎖的時(shí)候,必須按照申請(qǐng)鎖的時(shí)間順序來(lái)依次獲得鎖,也就是隊(duì)列方式。非公平鎖則是競(jìng)爭(zhēng)獲取。
synchronized是非公平的,ReentrantLock默認(rèn)也是非公平的,但是可以實(shí)現(xiàn)公平的。
鎖可以綁定多個(gè)事件
一個(gè)ReentrantLock對(duì)象可以綁定多個(gè)Condition對(duì)象,而synchronized的鎖對(duì)象的wait、notify等方法只能實(shí)現(xiàn)一個(gè)隱含的條件。
如果要用到上面三個(gè)高級(jí)功能的話,建議使用ReentrantLock,但是如果基于性能考慮的話,優(yōu)先考慮使用synchronized來(lái)進(jìn)行同步。
在jdk1.6之前,synchronized在多線程下吞吐量下降很?chē)?yán)重,ReentrantLock表現(xiàn)穩(wěn)定。但是1.6之后,性能就差不多了,而且以后虛擬機(jī)的優(yōu)化也是偏向synchronized。
互斥同步最主要的問(wèn)題就是進(jìn)行線程阻塞和喚醒所帶來(lái)的性能問(wèn)題,因此這種同步也是阻塞同步。從處理問(wèn)題角度來(lái)講,互斥同步是一種悲觀的并發(fā)策略,總是認(rèn)為只要不去做正確的同步措施(例如加鎖),那就肯定會(huì)出問(wèn)題,無(wú)論共享數(shù)據(jù)是否會(huì)出現(xiàn)競(jìng)爭(zhēng),它都會(huì)去加鎖同步。
3.2 非阻塞同步隨著硬件指令集的發(fā)展,出現(xiàn)了基于沖突檢測(cè)的樂(lè)觀并發(fā)策略。
通俗來(lái)講就是,先進(jìn)行操作,如果沒(méi)有其他線程爭(zhēng)用共享數(shù)據(jù),那操作就成功了;如果共享數(shù)據(jù)有爭(zhēng)用,產(chǎn)生了沖突,那就再采取其他的補(bǔ)償措施,這種樂(lè)觀的并發(fā)策略的許多實(shí)現(xiàn)都不需要把線程掛起,因此這種同步操作稱(chēng)為非阻塞同步。
最常見(jiàn)的補(bǔ)償措施就是不斷的重試,直到成功為止。
對(duì)于非阻塞同步來(lái)講,最重要的一個(gè)硬件指令是比較并交換(CAS)。java的Unsafe類(lèi)里面的某些方法被編譯之后,就成了一條平臺(tái)相關(guān)的處理器CAS指令,沒(méi)有方法調(diào)用的過(guò)程。
Unsafe類(lèi)不是提供給用戶(hù)程序調(diào)用的類(lèi),不使用反射的話,只能通過(guò)使用其他java api來(lái)間接使用。java的concurrent包里的AtomicInteger整數(shù)原子類(lèi)的compareAndSet和getAndIncrement方法使用了Unsafe類(lèi)的CAS操作。
CAS操作會(huì)出現(xiàn)“ABA”問(wèn)題:如果一個(gè)變量初始被讀取是A,最終被賦值的時(shí)候檢查到仍然是A,但是在讀取和賦值這段時(shí)間里,有可能被其他線程改為B,后來(lái)又改成了A。那么CAS就認(rèn)為沒(méi)有改變過(guò)。大部分情況下ABA也不會(huì)影響程序并發(fā)的正確性。
3.3 無(wú)同步方案要保證線程安全,不一定就得需要數(shù)據(jù)的同步,兩者沒(méi)有因果關(guān)系。如果一個(gè)方法不涉及共享數(shù)據(jù),那它自然就不用同步,有些代碼天生就是線程安全的,比如:
可重入代碼
也叫純代碼,可以在代碼執(zhí)行的任何時(shí)刻中斷它,轉(zhuǎn)而去執(zhí)行別的代碼(包括遞歸調(diào)用自己),而控制權(quán)返回后,原來(lái)的程序不會(huì)出現(xiàn)錯(cuò)誤。
所有可重入的代碼都是線程安全的,但是線程安全的代碼不一定是可重入的。
可重入代碼的一些特征是:
不依賴(lài)存儲(chǔ)在堆上的數(shù)據(jù)和公用系統(tǒng)資源
用的狀態(tài)量都是參數(shù)傳入
不調(diào)用不可重入代碼
如果一個(gè)方法是結(jié)果是可預(yù)測(cè)的,只要輸入了相同的數(shù)據(jù),就能返回相同的結(jié)果,那它就滿(mǎn)足可重入性要求,當(dāng)然也就是線程安全的。
線程本地存儲(chǔ)
如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享,那就看看這些共享數(shù)據(jù)的代碼能不能在同一個(gè)線程中運(yùn)行?如果把這些共享數(shù)據(jù)的可見(jiàn)范圍放在同一個(gè)線程之內(nèi),這樣無(wú)需進(jìn)行同步也可以做到線程安全。
符合這種特點(diǎn)的應(yīng)用有很多,比如:
大部分的消息隊(duì)列的生產(chǎn)者——消費(fèi)者模式。
web交互模型中的一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)服務(wù)器線程的處理方式。
在java中,如果一個(gè)變量要被某個(gè)線程獨(dú)享,就可以用ThreadLocal來(lái)實(shí)現(xiàn)線程本地存儲(chǔ)的功能。
4 寫(xiě)在最后通過(guò)對(duì)線程安全的仔細(xì)研究,終于理解了函數(shù)式編程為什么是天然的支持高并發(fā)了。函數(shù)式編程里的不可變對(duì)象和可重入代碼,都不會(huì)出現(xiàn)線程安全的問(wèn)題。這也是為什么現(xiàn)在函數(shù)式編程越來(lái)越火的一個(gè)重要原因。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70711.html
摘要:并發(fā)模塊本身有兩種不同的類(lèi)型進(jìn)程和線程,兩個(gè)基本的執(zhí)行單元。調(diào)用以啟動(dòng)新線程。在大多數(shù)系統(tǒng)中,時(shí)間片發(fā)生不可預(yù)知的和非確定性的,這意味著線程可能隨時(shí)暫停或恢復(fù)。 大綱 什么是并發(fā)編程?進(jìn)程,線程和時(shí)間片交織和競(jìng)爭(zhēng)條件線程安全 策略1:監(jiān)禁 策略2:不可變性 策略3:使用線程安全數(shù)據(jù)類(lèi)型 策略4:鎖定和同步 如何做安全論證總結(jié) 什么是并發(fā)編程? 并發(fā)并發(fā)性:多個(gè)計(jì)算同時(shí)發(fā)生。 在現(xiàn)代...
摘要:什么時(shí)候會(huì)出現(xiàn)線程不安全操作并非原子。只有單個(gè)組件,且它是線程安全的。這種情況下,就是的線程安全實(shí)際是委托給了整個(gè)表現(xiàn)出了線程安全。 當(dāng)多個(gè)線程去訪問(wèn)某個(gè)類(lèi)時(shí),如果類(lèi)會(huì)表現(xiàn)出我們預(yù)期出現(xiàn)的行為,那么可以稱(chēng)這個(gè)類(lèi)是線程安全的。 什么時(shí)候會(huì)出現(xiàn)線程不安全? 操作并非原子。多個(gè)線程執(zhí)行某段代碼,如果這段代碼產(chǎn)生的結(jié)果受不同線程之間的執(zhí)行時(shí)序影響,而產(chǎn)生非預(yù)期的結(jié)果,即發(fā)生了競(jìng)態(tài)條件,就會(huì)...
摘要:是需要我們?nèi)ヌ幚砗芏嗍虑椋瑸榱朔乐苟嗑€程給我們帶來(lái)的安全和性能的問(wèn)題下面就來(lái)簡(jiǎn)單總結(jié)一下我們需要哪些知識(shí)點(diǎn)來(lái)解決多線程遇到的問(wèn)題。 前言 不小心就鴿了幾天沒(méi)有更新了,這個(gè)星期回家咯。在學(xué)校的日子要努力一點(diǎn)才行! 只有光頭才能變強(qiáng) 回顧前面: 多線程三分鐘就可以入個(gè)門(mén)了! Thread源碼剖析 本文章的知識(shí)主要參考《Java并發(fā)編程實(shí)戰(zhàn)》這本書(shū)的前4章,這本書(shū)的前4章都是講解并發(fā)的基...
摘要:提供了線程安全的共享對(duì)象,在編寫(xiě)多線程代碼時(shí),可把不安全的整個(gè)變量封裝進(jìn),或者把該對(duì)象與線程相關(guān)的狀態(tài)使用保存并不能替代同步機(jī)制,兩者面向的問(wèn)題領(lǐng)域不同。 ThreadLocal類(lèi) 使用ThreadLocal類(lèi)可以簡(jiǎn)化多線程編程時(shí)的并發(fā)訪問(wèn),使用這個(gè)工具類(lèi)可以很簡(jiǎn)捷地隔離多線程程序的競(jìng)爭(zhēng)資源。Java5之后,為T(mén)hreadLocal類(lèi)增加了泛型支持,即ThreadLocal Threa...
摘要:線程安全問(wèn)題都是由全局變量及靜態(tài)變量引起的。常量始終是線程安全的,因?yàn)橹淮嬖谧x操作。局部變量是線程安全的。有狀態(tài)對(duì)象,就是有實(shí)例變量的對(duì)象,可以保存數(shù)據(jù),是非線程安全的。 前言 有多少人在使用Spring框架時(shí),很多時(shí)候不知道或者忽視了多線程的問(wèn)題? ??因?yàn)閷?xiě)程序時(shí),或做單元測(cè)試時(shí),很難有機(jī)會(huì)碰到多線程的問(wèn)題,因?yàn)闆](méi)有那么容易模擬多線程測(cè)試的環(huán)境。那么當(dāng)多個(gè)線程調(diào)用同一個(gè)bean的時(shí)...
閱讀 2395·2021-11-11 16:54
閱讀 1204·2021-09-22 15:23
閱讀 3644·2021-09-07 09:59
閱讀 1990·2021-09-02 15:41
閱讀 3283·2021-08-17 10:13
閱讀 3037·2019-08-30 15:53
閱讀 1235·2019-08-30 13:57
閱讀 1210·2019-08-29 15:16