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