1.線程安全
如果不用考慮這些線程在運行時環(huán)境下的調(diào)度和交替執(zhí)行显押,也不需要進行額外的同步,或者在調(diào)用方進行任何其他的協(xié)調(diào)操作包券,調(diào)用這個對象的行為都可以獲得正確的結(jié)果首妖,那這個對象是線程安全的。(要求代碼本身封裝好了保障手段塔次,令調(diào)用者無需關(guān)心多線程的問題方篮,更無需自己采取任何措施保障線程安全)
1.1Java語言中的線程安全
按照線程的“安全程序”由強至弱劃分,可以分為5類
- 不可變
如果一個不可變的對象創(chuàng)建了(例如被final修飾)励负,那不管哪個線程看到的永遠都是一個值恭取,自然就是線程安全的。
String的substring()replace()等方法都不會影響他原來的值熄守,只是返回一個新構(gòu)造的字符串對象
2.絕對線程安全
絕對線程安全將是最上面定義的內(nèi)容蜈垮,但是通常沒有絕對線程安全。
比如一些常說的線程安全的類裕照,如Vector是一個線程安全的容器攒发,因為內(nèi)部的add(),get()方法都是被synchronized修飾的。但是仍然要在方法調(diào)用端做額外的同步措施 - 相對線程安全
這個就是普遍說的線程安全晋南,比如Vector,HashTable - 線程兼容
線程兼容指對象本身并不是線程安全的惠猿,但是可以通過正確的調(diào)用手段來保障對象并發(fā)環(huán)境中可以安全的使用,例如 ArrayList,HashMap - 線程對立
無論調(diào)用端怎么做都無法保障線程安全
1.2線程安全的實現(xiàn)方法
1.2.1互斥同步(阻塞同步)
同步是指多個線程并發(fā)訪問共享數(shù)據(jù)時负间,保證共享數(shù)據(jù)在同一個時刻只被一個(或者是一些偶妖,使用信號量時)線程使用。而互斥是實現(xiàn)同步的一種手段政溃,互斥鎖方法趾访,同步是目的
最基本的互斥手段是使用synchronized
- synchronized關(guān)鍵字經(jīng)過編譯后,會在同步代碼塊的前后分別形成monitorenter和monitorexit這兩個字節(jié)碼指令(XXXenter和XXXexit這樣就好記了)董虱,這兩個字節(jié)碼都需要一個reference類型的參數(shù)來指明要鎖定和解鎖的對象扼鞋。
- 如果Java程序中的synchronized明確指定了對象參數(shù)申鱼,那就算這個對象的reference;如果沒有明確指定云头,那家根據(jù)synchronized修飾的是實例方法還是類方法捐友,去取對于的對象實例或Class對象作為鎖對象
- 根據(jù)虛擬機規(guī)范要求,在執(zhí)行monitorenter指令時溃槐,首先要嘗試獲取對象的鎖匣砖。如果這個對象沒有被鎖,或當前線程已經(jīng)擁有了那個對象的鎖昏滴,就把鎖的計數(shù)器加1脆粥,相應(yīng)的,在執(zhí)行monitorexit指令時會將鎖計數(shù)器減1影涉,當計數(shù)器值為0時变隔,鎖就被釋放。如果獲取對象鎖失敗蟹倾,那么當前線程就要阻塞等待匣缘,直到對象鎖被另外一個線程釋放為止
- synchronized同步塊對同一條線程來說是可重入的,不會出現(xiàn)自己把自己瑣死的問題鲜棠。同步快在已進入的線程執(zhí)行完之前肌厨,會阻塞后面其他線程的進入。
- synchronized雖然看起來是“萬能”的豁陆,但不建議濫用的原因是因為消耗大柑爸。因為Java線程是映射到操作系統(tǒng)原生線程的,如果要阻塞或喚醒一個線程都需要原生線程幫忙完成盒音,這就需要從用戶態(tài)轉(zhuǎn)換到核心態(tài)中表鳍,因此狀態(tài)轉(zhuǎn)換需要耗費很多的處理器時間,所以 synchronized是Java語言中的一個重量級操作
除了synchronized之外祥诽,java.util.concurrent包下的重入鎖(ReentrantLock)來
實現(xiàn)同步譬圣,重入鎖多了一些高級功能:等待可中斷、可實現(xiàn)公平鎖雄坪、鎖可以綁定多個條件
- 等待可中斷:當持有鎖的線程長期不釋放鎖的時候厘熟,正在等待的線程可以選擇放棄等待,改為處理其他事情维哈,可中斷性對處理執(zhí)行時間非常長的同步快很有幫助
- 公平鎖:是指多個線程等待同一個鎖時绳姨,必須按照申請鎖的時間順序來一次獲得鎖;而非公平鎖無法保證這一點阔挠,在鎖被釋放時飘庄,任何一個等待鎖的線程都有機會獲得鎖。synchronized中鎖鎖就鎖非公平的谒亦,ReentrantLock默認鎖也是非公平的竭宰,但是可以通過布爾值的構(gòu)造函數(shù)要求使用公平鎖
- 以綁定多個條件:指一個ReentrantLock對象可以同時綁定多個Condition對象,而在synchronized中份招,鎖對象的wait()和notify()或notifyAll()方法可以實現(xiàn)一個隱含的條件切揭,如果要和多余一個條件關(guān)聯(lián)的時候,就不得不額外添加一個鎖锁摔,而ReentrantLock則無需這樣做廓旬,只需要多次調(diào)用newCondition()方法即可
JDK1.6之前ReentrantLock性能比synchronized好,1.6后持平
1.2.2非阻塞同步
- 互斥同步最主要的問題就是進行線程阻塞和喚醒所帶來的性能問題谐腰,所以也叫阻塞同步孕豹。從處理問題的方式說,互斥同步(阻塞同步)屬于一種悲觀的并發(fā)策略(所以叫叫悲觀鎖么十气?励背?)
- 悲觀的并發(fā)策略:總是認為主要不去做做正確的同步措施(如:加鎖),那家肯定會出現(xiàn)問題砸西,無論共享數(shù)據(jù)是否真的會出現(xiàn)競爭叶眉,它都要進行加鎖、用戶態(tài)到核心態(tài)轉(zhuǎn)換芹枷、維護鎖計數(shù)器和檢查是否有被阻塞的線程需要被喚醒
- 隨著硬件指令的發(fā)展衅疙,新的選擇:基于沖突檢測的樂觀并發(fā)策略:先執(zhí)行,如果沒有其他線程爭用共享數(shù)據(jù)鸳慈,那操作就成功了饱溢,如果共享數(shù)據(jù)有爭用,產(chǎn)生了沖突走芋,那就再采取其他的補償措施(最常見的補償措施家是不斷的重試绩郎,知直到成功),這種樂觀的并發(fā)策略的許多實現(xiàn)都不需要把線程掛起翁逞,因此這種同步操作稱為非阻塞同步
1.2.3無同步方案
要保證線程安全嗽上,并不一定就要進行同步,兩者沒有因果關(guān)系熄攘,同步只是保證共享數(shù)據(jù)爭用時的正確性的手段兽愤,如果一個方法本來就不涉及共享數(shù)據(jù),那它自然就無需任何同步措施去保證正確性挪圾,因此會有一些代碼天生就是線程安全的
- 可重入代碼
- 線程本地存儲-ThreadLocal
每個線程的Thread對象中都有一個ThreadLocalMap對象浅萧,這個對象存儲了一組以ThreadLocal.threadLocalHashCode為健,以本地線程變量為值的K-V值對哲思,ThreadLocal對象就是當前線程的ThreadLocalMap訪問入口洼畅,每一個ThreadLocal對象都包含了一個獨一無二的threadLocalHashCode值,使用這個值就可以在線程K-V值中找回對于的本地線程變量
2.鎖優(yōu)化
都是虛擬機開發(fā)團隊優(yōu)化鎖機制棚赔,一堆嗶哩吧啦的帝簇,也不知道咋記錄徘郭。想知道具體的,就去百度或者翻書吧
2.1 自旋鎖與自適應(yīng)自旋
2.2鎖消除
2.3鎖粗化
簡單的說就是一個類里丧肴,一塊代碼加一個鎖残揉,還不如整個類都鎖起來。大致是這個意思