Java多線程編程核心技術(shù)2——同步

一. 對(duì)象及變量的并發(fā)訪問(wèn)

非線程安全會(huì)發(fā)生在多個(gè)線程并發(fā)訪問(wèn)同一個(gè)對(duì)象的實(shí)例變量時(shí)身弊,會(huì)產(chǎn)生臟讀,讀取到的數(shù)據(jù)是被更改過(guò)的吕漂。而線程安全各個(gè)線程獲得的實(shí)例變量的值都是經(jīng)過(guò)同步處理的廷痘,不會(huì)出現(xiàn)臟讀。


1.線程是否安全呢构灸?

(1) 如果是方法內(nèi)部的私有變量上渴,不存在非線程安全問(wèn)題。

(2) 多線程訪問(wèn)的是同一個(gè)對(duì)象的實(shí)例變量時(shí),有可能出現(xiàn)線程不安全問(wèn)題稠氮。

(3) 多個(gè)線程訪問(wèn)的是同步方法的話曹阔,一定是線程安全的。

(4) 多個(gè)線程對(duì)應(yīng)的是多個(gè)對(duì)象時(shí)隔披,出現(xiàn)的結(jié)果就會(huì)是異步的赃份,但是線程安全。


2. synchronized關(guān)鍵字

(1) synchronized獲得的是對(duì)象鎖奢米,而不是把synchronized下面的方法或者代碼塊當(dāng)做鎖抓韩。

(2) synchronized聲明的方法一定是排隊(duì)執(zhí)行的(同步的),只有共享資源的讀寫(xiě)訪問(wèn)才需要同步化恃慧。

(3) 線程A和線程B訪問(wèn)同一個(gè)object對(duì)象的兩個(gè)同步的方法园蝠,線程A先獲取object對(duì)象的Lock鎖,B線程可以以異步的方式調(diào)用object對(duì)象中的非同步方法痢士,但是想訪問(wèn)該對(duì)象的同步方法的話彪薛,必須得等待,不管想訪問(wèn)的是不是和線程A同一個(gè)同步方法怠蹂。

(4) synchronized有鎖重入的功能善延,即自己可以再次獲取自己的內(nèi)部鎖,可重入鎖也支持父子類(lèi)繼承關(guān)系中城侧,即子類(lèi)可以通過(guò)可重入鎖訪問(wèn)父類(lèi)的方法易遣。若不可重入的話,就會(huì)造成死鎖嫌佑。

(5) 當(dāng)一個(gè)線程執(zhí)行的代碼出現(xiàn)異常時(shí)豆茫,其持有的鎖會(huì)自動(dòng)釋放(即該線程結(jié)束執(zhí)行)。

(6) 同步不具有繼承性屋摇。

(7) 鎖定的對(duì)象改變揩魂,比如String,可能導(dǎo)致同步鎖無(wú)效(因?yàn)殒i變了)炮温。但是只要對(duì)象不變火脉,對(duì)象的屬性被改變,鎖還是同一個(gè)柒啤。


3.synchronized同步語(yǔ)句塊

synchronized同步方法是對(duì)當(dāng)前對(duì)象加鎖倦挂,同步代碼塊則是對(duì)某一個(gè)對(duì)象加鎖。synchronized同步代碼塊運(yùn)行效率應(yīng)該大于同步方法担巩。

synchronized(this):也是鎖定當(dāng)前對(duì)象的方援。

synchronized(非this對(duì)象):使用同步代碼塊來(lái)鎖定非this對(duì)象,則synchronized(非this對(duì)象)與同步方法是異步的涛癌,不與其他鎖this同步方法爭(zhēng)搶this鎖肯骇,可以大大提高效率窥浪。synchronized同步代碼塊都不采用String作為鎖對(duì)象,易造成死鎖笛丙。


4.synchronized關(guān)鍵字加到static靜態(tài)方法上是給Class類(lèi)加上鎖(Class鎖可以對(duì)類(lèi)得所有對(duì)象實(shí)例起作用),而加到非static靜態(tài)方法上是給對(duì)象上鎖假颇。

synchronized關(guān)鍵字加到static靜態(tài)方法上是給Class類(lèi)加上鎖 = synchronized(xxx.Class){}


5.多線程的死鎖

因?yàn)椴煌木€程都在等待根本不可能被釋放的鎖胚鸯,從而導(dǎo)致所有的任務(wù)都無(wú)法繼續(xù)執(zhí)行。

比如線程A持有了鎖1在等待鎖2笨鸡,線程A持有了鎖2在等待鎖1--》導(dǎo)致死鎖姜钳。

解決方案:不使用嵌套的synchronized代碼結(jié)構(gòu)。


6.內(nèi)置類(lèi)與靜態(tài)內(nèi)置類(lèi)(補(bǔ)充介紹)

非靜態(tài)內(nèi)置類(lèi):指定對(duì)象.new 內(nèi)置類(lèi)();

靜態(tài)內(nèi)置類(lèi):可直接new 內(nèi)置類(lèi)();


7.volatile關(guān)鍵字:使變量在多個(gè)線程中可見(jiàn)

作用:強(qiáng)制從公共堆棧中獲取變量的值形耗,而不是從線程私有數(shù)據(jù)棧中獲取哥桥。

※ 在JVM被設(shè)置為-server模式時(shí)是為了線程運(yùn)行的效率,線程一直在私有堆棧中獲取變量的值激涤。

在-server模式下拟糕,公共堆棧的值和線程私有數(shù)據(jù)棧的值不同步,加了volatile后就會(huì)強(qiáng)制從公共堆棧中讀寫(xiě)倦踢。

volatile和synchronized的比較:

(1) volatile只能修飾變量送滞,是輕量級(jí)實(shí)現(xiàn),所以性能比synchronized好辱挥。

(2) 多線程訪問(wèn)volatile不會(huì)阻塞犁嗅,而訪問(wèn)synchronized會(huì)阻塞。

(3) volatile能保證數(shù)據(jù)可見(jiàn)性晤碘,但不具備同步性褂微,不支持原子性;而synchronized可以保證原子性园爷,也可以間接保證可見(jiàn)性宠蚂,因?yàn)樗麜?huì)將私有內(nèi)存和共有內(nèi)存中的數(shù)據(jù)做同步。

(4) 兩者功能屬性不同腮介,synchronized解決的是多個(gè)線程之間訪問(wèn)資源的同步性肥矢;

volatile解決變量在多個(gè)線程之間的可見(jiàn)性,即:在多個(gè)線程可以感知實(shí)例變量被修改了叠洗,并且可以獲得最新的值引用甘改,也就是用多線程讀取共享變量時(shí)能獲得最新值引用。

volatile int i

i++;

i++有如下三個(gè)步驟:

(1) 從內(nèi)存中獲取i的值灭抑;

(2)計(jì)算i的值十艾;

(3) 將i的值寫(xiě)入內(nèi)存中。

這樣的操作不是一個(gè)原子操作(聯(lián)想:synchronized修飾的方法或者代碼段可以看做一個(gè)整體腾节,因此具有原子性)忘嫉,比如線程B要提取i的值時(shí)荤牍,線程A還未將計(jì)算好的i的值放回內(nèi)存,則線程B取出來(lái)的i的值還是線程A計(jì)算前的值庆冕。--》線程不安全


8.AtomicInteger(AtomicLong等)

private AtomicInteger count = new AtomicInteger(0);

System.out.println(count.incrementAndGet());//自動(dòng)加1//decrementAndGet()自動(dòng)減1

public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }


Compare And Swap(CAS):首先康吵,CPU 會(huì)將內(nèi)存中將要被更改的數(shù)據(jù)與期望的值做比較。然后访递,當(dāng)這兩個(gè)值相等時(shí)晦嵌,CPU 才會(huì)將內(nèi)存中的數(shù)值替換為新的值。否則便不做操作拷姿。最后惭载,CPU 會(huì)將舊的數(shù)值返回。這一系列的操作是原子的响巢。簡(jiǎn)單來(lái)說(shuō)描滔,CAS 的含義是“我認(rèn)為原有的值應(yīng)該是什么,如果是踪古,則將原有的值更新為新值含长,否則不做修改,并告訴我原來(lái)的值是多少”灾炭。

CAS有3個(gè)操作數(shù)茎芋,內(nèi)存值V,舊的預(yù)期值A(chǔ)蜈出,要修改的新值B田弥。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B铡原,否則返回V偷厦。這是一種樂(lè)觀鎖的思路,它相信在它修改之前燕刻,沒(méi)有其它線程去修改它只泼;而Synchronized是一種悲觀鎖,它認(rèn)為在它修改之前卵洗,一定會(huì)有其它線程去修改它请唱,悲觀鎖效率很低。下面來(lái)看一下AtomicInteger是如何利用CAS實(shí)現(xiàn)原子性操作的过蹂。

但是CAS也是有問(wèn)題存在的:

CAS的ABA問(wèn)題

1.進(jìn)程P1在共享變量中讀到值為A

2.P1被搶占了十绑,進(jìn)程P2執(zhí)行

3.P2把共享變量里的值從A改成了B,再改回到A酷勺,此時(shí)被P1搶占本橙。

4.P1回來(lái)看到共享變量里的值沒(méi)有被改變,于是繼續(xù)執(zhí)行脆诉。

雖然P1以為變量值沒(méi)有改變甚亭,繼續(xù)執(zhí)行了贷币,但是這個(gè)會(huì)引發(fā)一些潛在的問(wèn)題。ABA問(wèn)題最容易發(fā)生在lock free 的算法中的亏狰,CAS首當(dāng)其沖役纹,因?yàn)?b>CAS判斷的是指針的地址。如果這個(gè)地址被重用了呢暇唾,問(wèn)題就很大了字管。(地址被重用是很經(jīng)常發(fā)生的,一個(gè)內(nèi)存分配后釋放了信不,再分配,很有可能還是原來(lái)的地址)


還有一種情況是:?jiǎn)为?dú)一個(gè)AtomicInteger.incrementAndGet()是線程安全的亡呵,但是同時(shí)兩個(gè)AtomicInteger.incrementAndGet()就不一定是線程安全的了抽活,即兩個(gè)方法之間不是原子的。

public static AtomicLong count = new AtomicLong();

public void addNum(){

System.out.println(count.addAndGet(100));

System.out.println(count.addAndGet(1));

}

多個(gè)線程調(diào)用addNum()時(shí)锰什,線程A加了100下硕,還沒(méi)來(lái)得及加1,線程B就進(jìn)來(lái)加了100汁胆。

解決方案:

public static AtomicLong count = new AtomicLong();

synchronized public void addNum(){

System.out.println(count.addAndGet(100));

System.out.println(count.addAndGet(1));

}


9.synchronized代碼塊也具有volatile同步的功能

線程A調(diào)用runMethod()梭姓,線程B調(diào)用stopMethod(),持有的是同一把鎖嫩码。

當(dāng)線程A調(diào)用完runMethod()后誉尖,打印不出"停下來(lái)了!"的铸题,因?yàn)樗姥h(huán)铡恕,被A鎖死了。

各線程間的數(shù)據(jù)值沒(méi)有可見(jiàn)性丢间。

private Boolean isContinueRun = true;

public void runMethod(){

??? while(isContinueRun){

??? }

??? System.out.println("停下來(lái)了探熔!");

}

public void stopMethod(){

??? isContinueRun = false;

}

解決方案如下,成功打印"停下來(lái)了烘挫!"

private Boolean isContinueRun = true;

public void runMethod(){

??? private anyString = new String();

??? while(isContinueRun){

??????? synchronized(anyString){

??????? }

??? }

??? System.out.println("停下來(lái)了诀艰!");

}

public void stopMethod(){

??? isContinueRun = false;

}

關(guān)鍵字synchronized 保證同一時(shí)刻,只有一個(gè)線程可以執(zhí)行某一個(gè)方法或某一個(gè)代碼塊饮六。包含兩種特性:互斥性和可見(jiàn)性其垄。

?不僅可以解決一個(gè)線程看到對(duì)象處于不一致的狀態(tài),還可以保證進(jìn)入同步方法或代碼塊的每個(gè)線程喜滨,都可以看到由同一個(gè)鎖保護(hù)之前所有的修改結(jié)果捉捅。(外連互斥,內(nèi)修可見(jiàn)虽风。)


二. 鎖的使用

Lock也能實(shí)現(xiàn)同步的效果棒口,在使用上更加方便寄月。

1. ReentrantLock類(lèi)? -- Lock lock = new ReentrantLock();

(1) 使用ReentrantLock.lock()獲取鎖(加鎖),線程就擁有了“對(duì)象監(jiān)視器”无牵;

其他線程只有等待ReentrantLock.unlock()釋放鎖(解鎖)漾肮,再次爭(zhēng)搶獲得鎖。

效果和synchronized一致茎毁,但線程執(zhí)行順序是隨機(jī)的克懊。

(2) 關(guān)鍵字與wait()/notify()/notifyAll():實(shí)現(xiàn)等待/通知模式,但是notifyAll()的話七蜘,需要通知所有處于WAITING狀態(tài)的線程谭溉,會(huì)出現(xiàn)相當(dāng)大的效率問(wèn)題。

ReentrantLock和Condition對(duì)象也同樣可以實(shí)現(xiàn)橡卤。在一個(gè)Lock對(duì)象里可以創(chuàng)建多個(gè)Condition(即對(duì)象監(jiān)視器)實(shí)例扮念,可以實(shí)現(xiàn)多路通知功能;實(shí)例對(duì)象可以注冊(cè)在指定的Condition中碧库,從而可以有選擇地進(jìn)行線程通知柜与,在調(diào)度線程上更加靈活。

package lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockService {
?private Lock lock = new ReentrantLock();
?public Condition condition = lock.newCondition();
?public void await(){
??try {
???lock.lock();
???condition.await();//必須在調(diào)用await()之前先調(diào)用lock()以獲得同步監(jiān)視器
??} catch (InterruptedException e) {
???e.printStackTrace();
??}
?}
?
?public void signal(){
??try {
???lock.lock();
???condition.signal();//還有condition.signalAll();
??} finally{
???lock.unlock();
??}??
?}
}

(3) signalAll()

public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
//use:
conditionA.signal(); //or conditionB.signal();

喚醒指定種類(lèi)的線程嵌灰,如conditionA.signal(); 只有用了conditionA的線程被喚醒弄匕。

(4) 公平鎖與非公平鎖

公平鎖:線程獲取鎖的順序是按照線程加鎖的順序來(lái)分配的(FIFO);new ReentrantLock(true);//不一定百分百FIFO沽瞭,但是基本呈有序迁匠。

非公平鎖(默認(rèn)):鎖的搶占機(jī)制,隨機(jī)獲得鎖秕脓。new ReentrantLock(false);


2.相關(guān)方法介紹

(1) int getHoldCount():查詢當(dāng)前線程保持此鎖定的個(gè)數(shù)柒瓣,也就是調(diào)用lock()的次數(shù)。

(2) int getQueueLength():返回正獲取此鎖定的線程估計(jì)數(shù)吠架,如5個(gè)線程芙贫,一個(gè)線程調(diào)用了await(),還有4個(gè)線程在等待鎖的釋放傍药。

(3) int getWaitQueueLength(Condition condition):比如有5個(gè)線程磺平,每個(gè)線程都執(zhí)行了同一個(gè)condition對(duì)象的await(),則結(jié)果為5拐辽。

(4) boolean hasQueuedThread(Thread thread):查詢指定的線程是否正在等待此鎖定拣挪。

(5) boolean hasWaiters(Condition condition):是否有線程正在等待與此鎖定有關(guān)的condition條件。

(6) boolean isFair():判斷是不是公平鎖俱诸。

(7) boolean isHeldByCurrentThread():查詢當(dāng)前線程是否保持此鎖定菠劝。

(8) boolean? isLocked():查詢此鎖定是否由任意線程保持。

(9) void lockInterruptibly():如果當(dāng)前線程未被中斷睁搭,則獲取鎖定赶诊,如果已經(jīng)被中斷則出現(xiàn)異常笼平。

(10) boolean tryLock():僅在調(diào)用時(shí)鎖定未被另一個(gè)線程保持的情況下,才獲取該鎖定舔痪。

(11) boolean tryLock(long timeout,TimeUnit unit):若在給定等待時(shí)間內(nèi)沒(méi)有被另一個(gè)線程保持寓调,且當(dāng)前線程未被中斷,則獲取該鎖定锄码。

(12) Condition.awaitUninterruptibly():在WAITING情況下interrupt()不會(huì)拋出異常夺英。

(13) Condition.awaitUntil(time):線程在等待時(shí)間到達(dá)前,可以被其他線程喚醒滋捶。


3.ReentranReadWriteLock類(lèi)

共享鎖:讀操作相關(guān)的鎖痛悯;排他鎖:寫(xiě)操作相關(guān)的鎖。

讀寫(xiě)重窟,寫(xiě)讀灸蟆,寫(xiě)寫(xiě)都是互斥的;讀讀是異步的亲族,非互斥的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末可缚,一起剝皮案震驚了整個(gè)濱河市霎迫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帘靡,老刑警劉巖知给,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異描姚,居然都是意外死亡涩赢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)轩勘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)筒扒,“玉大人,你說(shuō)我怎么就攤上這事绊寻』ǘ眨” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵澄步,是天一觀的道長(zhǎng)冰蘑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)村缸,這世上最難降的妖魔是什么祠肥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮梯皿,結(jié)果婚禮上仇箱,老公的妹妹穿的比我還像新娘县恕。我一直安慰自己,他們只是感情好工碾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布弱睦。 她就那樣靜靜地躺著,像睡著了一般渊额。 火紅的嫁衣襯著肌膚如雪况木。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天旬迹,我揣著相機(jī)與錄音火惊,去河邊找鬼。 笑死奔垦,一個(gè)胖子當(dāng)著我的面吹牛屹耐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椿猎,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼惶岭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了犯眠?” 一聲冷哼從身側(cè)響起按灶,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筐咧,沒(méi)想到半個(gè)月后鸯旁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡量蕊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年铺罢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片残炮。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡韭赘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出势就,到底是詐尸還是另有隱情辞居,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布蛋勺,位于F島的核電站瓦灶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏抱完。R本人自食惡果不足惜贼陶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碉怔,春花似錦烘贴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至芹啥,卻和暖如春锻离,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墓怀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工汽纠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人傀履。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓虱朵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钓账。 傳聞我的和親對(duì)象是個(gè)殘疾皇子碴犬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容