線程安全定義
《Java Concurrency In Practice》一書中定義“線程安全”:當(dāng)多個線程訪問一個對象時(shí)奥此,如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行虐拓,也不需要進(jìn)行額外的同步嘁捷,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作厦画,調(diào)用這個對象的行為都可以獲得正確的結(jié)果,那這個對象是線程安全的翘紊。
這個定義比較嚴(yán)謹(jǐn)换薄,它要求線程安全的代碼都必須具備一個特征:代碼本身封裝了所有必要的正確性保障手段(如互斥同步等),令調(diào)用者無須關(guān)心多線程的問題融师,更無須自己采取任何措施來保證多線程的正確調(diào)用右钾。
Java語言中線程安全中,各種操作共享的數(shù)據(jù)分為以下5類:不可變旱爆、絕對線程安全舀射、相對線程安全、線程兼容和線程對立怀伦。
不可變
不可變的對象一定是線程安全的脆烟,不論對象的方法實(shí)現(xiàn)還是方法的調(diào)用者,都不需要采取任何線程安全保障措施房待⌒细幔“不可變”帶來的安全性是最簡單和最純粹的。
在Java語言中桑孩,如果共享數(shù)據(jù)是一個基本數(shù)據(jù)類型拜鹤,只要final關(guān)鍵字修飾就可以保證它是不可變的。如果共享對象是一個對象流椒,那就需要保證對象的行為不會對其狀態(tài)產(chǎn)生任何影響才行敏簿。
絕對線程安全
完全滿足上訴線程安全的定義,這個定義其實(shí)是很嚴(yán)格的,通常需要付出很大的代價(jià)才能達(dá)到惯裕,甚至有時(shí)候是不切實(shí)際的代價(jià)温数。
相對線程安全
相對的線程安全就是我們通常意義上所講的線程安全,它需要保證對這個對象單獨(dú)的操作是線程安全的蜻势。我們在調(diào)用的時(shí)候不需要做額外的保障措施撑刺,但是對于一些特定順序的連續(xù)調(diào)用,就可能需要在調(diào)用端使用額外的同步手段來保證調(diào)用的正確性握玛。在Java語言中猜煮,大部分的線程安全類都屬于這種類型,例如Vector败许、HashTable王带、Collections的synchronizedCollection()方法包裝的集合等。
線程兼容
線程兼容是指對象本身并不是線程安全的市殷,但是可以通過在調(diào)用端正確地使用同步手段來保證對象在并發(fā)環(huán)境中可以安全地使用愕撰,我們平常說一個類不是線程安全的,絕大多數(shù)時(shí)候指的是這種情況醋寝。Java API中大部分的類都是屬于線程兼容的搞挣,如ArrayList和HashMap等。
線程對立
線程對立指無論調(diào)用端是否采取了同步措施音羞,都無法在多線程環(huán)境中并發(fā)使用的代碼囱桨。由于Java語言天生就具備多線程特性,線程對立這種排斥多線程的代碼是很少出現(xiàn)嗅绰,而且通常都是有害的舍肠,應(yīng)當(dāng)避免。常見的例子有Thread類的suspend()和resume()方法窘面,System.setIn()翠语、System.setOut()和System.runFinalizersOnExit()等。
線程安全的實(shí)現(xiàn)
互斥同步
互斥同步(Mutual Exclusion & Synchronion)是常見的一種并發(fā)正確性保障手段财边。同步是指在多個線程并發(fā)訪問共享數(shù)據(jù)時(shí)肌括,保證共享數(shù)據(jù)在同一時(shí)刻只被一個(或者是一些,使用信號量的時(shí)候)線程使用酣难。而互斥是實(shí)現(xiàn)同步的一種手段谍夭,臨界區(qū)(Critical Section)、互斥量(Mutex)和信號量(Semaphore)都是主要的互斥實(shí)現(xiàn)方式憨募。因此這里面紧索,互斥是因,同步是果馋嗜,互斥是方法齐板,同步是目的。
在Java中葛菇,最基本的互斥同步方法就是synchronized關(guān)鍵字甘磨,synchronized關(guān)鍵字經(jīng)過編譯之后,會在同步塊的前后分別形成monitorenter和monitorexit這兩個字節(jié)碼指令眯停,這兩個字節(jié)碼都需要一個reference類型的參數(shù)來明確要鎖定和解鎖的對象济舆。synchronized同步塊對同一線程來說是可重入的 ,不會出現(xiàn)自己把自己鎖死的情況莺债,其次滋觉,同步塊在已進(jìn)入線程執(zhí)行完之前,會阻塞后面其他線程的進(jìn)入齐邦。
除了使用synchronized之外椎侠,我們還可以使用java.util.concurrent包中的重入鎖(ReentrantLock)來實(shí)現(xiàn)同步。相比于synchronized措拇,ReentrantLock增加了一些高級功能:
- 等待可中斷是指當(dāng)持有鎖的線程長期不釋放鎖的時(shí)候我纪,正在等待的線程可以選擇放棄等待,改為處理其他事情丐吓,可中斷特性對處理執(zhí)行非常長的同步塊很有幫助
- 公平鎖是指多個線程在等待同一個鎖時(shí)浅悉,必須按照申請鎖的時(shí)間順序來依次獲得鎖;而非公平鎖則不保證這一點(diǎn)券犁,在鎖被釋放時(shí)术健,任何一個等待鎖的線程都有機(jī)會獲得鎖。synchronized是非公平鎖粘衬,ReentrantLock默認(rèn)情況下是非公平的荞估,但是可以設(shè)置成公平鎖
- 鎖綁定多個條件是指一個ReentrantLock對象可以同時(shí)綁定多個Condition對象,而synchronized中稚新,鎖對象的wait()和notify()或者notifyAll()方法可以實(shí)現(xiàn)一個隱含的條件泼舱,如果要和多于一個條件關(guān)聯(lián)的時(shí)候,就不得不額外地添加一個鎖枷莉,而ReentrantLock則無須這樣做娇昙,只需要多次調(diào)用newCondition()方法即可。
非阻塞同步
互斥同步最主要的問題就是進(jìn)行線程阻塞和喚醒所帶來的性能問題笤妙,因此這種同步也稱為阻塞同步(Blocking Synchronization)冒掌。從出了問題的方式來說,互斥同步是一種悲觀的并發(fā)策略蹲盘,總是認(rèn)為要是不做正確的同步措施股毫,就肯定出現(xiàn)問題。隨著硬件指令集的發(fā)展召衔,可以采用基于沖突檢測的樂觀并發(fā)策略铃诬,通俗的將就是先進(jìn)行操作,產(chǎn)生了沖突,那就再采取其他補(bǔ)償措施趣席,這種樂觀的并發(fā)策略許多實(shí)現(xiàn)都不需要把線程掛起兵志,因此這種同步操作稱為非阻塞同步(Non-Blockinig Synchronization)。
無同步方案
要保證線程安全宣肚,并不是一定要進(jìn)行同步想罕,兩者沒有因果關(guān)系。同步只是保證共享數(shù)據(jù)爭用時(shí)的正確性的手段霉涨,如果一個方法本來就不涉及共享數(shù)據(jù)按价,那它自然就無須任何同步措施去保證正確性,因此會有一些代碼天生就是線程安全的笙瑟。
可重入代碼:這種代碼也叫做純代碼(Pure Code)楼镐,可以在代碼執(zhí)行的任何時(shí)刻中斷它,轉(zhuǎn)而去執(zhí)行另外一段代碼(包括遞歸調(diào)用本身)往枷,而在控制權(quán)返回后框产,原來的程序不會出現(xiàn)任何錯誤。相對線程安全來說师溅,可重入性是更基本的特性茅信,它可以保證線程安全,即所有的可重入代碼都是線程安全的墓臭,但是并不是所有的線程安全代碼都是可重入的蘸鲸。可重入代碼有一些共同特征窿锉,例如不依賴存儲在堆上的數(shù)據(jù)和公用的系統(tǒng)資源酌摇,用到的狀態(tài)量都由參數(shù)中傳入、不調(diào)用非可重入的方法等嗡载。
線程本地存儲(Thread Local Storage):如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享窑多,那就看看這些共享數(shù)據(jù)是否能保證在同一個線程中執(zhí)行,如果能保證洼滚,就可以把共享數(shù)據(jù)的可見范圍限制在同一線程之內(nèi)埂息,這樣無須同步也能保證下城之間不出現(xiàn)數(shù)據(jù)爭用的問題。
符合這種特點(diǎn)的應(yīng)用并不少遥巴,典型的就是消費(fèi)隊(duì)列架構(gòu)模式千康,都會將產(chǎn)品的消費(fèi)過程盡量在一個線程中消費(fèi)完。Java語言中铲掐,如果一個變量要被多線程訪問拾弃,可以使用volatile關(guān)鍵字聲明為“易變的”:如果一個變量要被某個線程獨(dú)享,可以通過java.lang.ThreadLocal類來實(shí)現(xiàn)線程本地存儲的功能摆霉。每個線程的Thread對象都有一個ThreadLocalMap對象豪椿,這個對象存儲了一組以ThreadLocal.threadLocalHashCode為鍵奔坟,以本地線程變量為值的K-V值對,ThreadLocal對象就是當(dāng)前線程的ThreadLocalMap的訪問入口搭盾,每個ThreadLocal對象都包含了獨(dú)一無二的threadLocalHashCode值咳秉,使用這個值就可以在線程K-V值對中找回對應(yīng)的本地線程變量。
參考資料
- 深入理解Java虛擬機(jī) JVM高級特性與最佳實(shí)踐 第2版