java并發(fā)編程基礎(chǔ)

修復(fù)多個(gè)線程訪問(wèn)同一個(gè)可變的狀態(tài)變量沒(méi)有使用合適的同步滨巴,所產(chǎn)生的問(wèn)題:

不在線程之間共享該狀態(tài)變量
將狀態(tài)變量修改為不可變的變量
在訪問(wèn)狀態(tài)變量時(shí)使用同步

線程安全性

當(dāng)多個(gè)線程訪問(wèn)某個(gè)類(lèi)時(shí)矮男,不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些線程將如何交替進(jìn)行湿硝,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同拆融,這個(gè)類(lèi)都能表現(xiàn)出正確的行為者吁,那么就稱這個(gè)類(lèi)是線程安全的刀闷。

原子性

原子性是指一個(gè)操作是不可中斷的根时,要么全部執(zhí)行成功要么全部執(zhí)行失敗,有著“同生共死”的感覺(jué)垛耳。及時(shí)在多個(gè)線程一起執(zhí)行的時(shí)候栅屏,一個(gè)操作一旦開(kāi)始飘千,就不會(huì)被其他線程所干擾堂鲜。我們先來(lái)看看哪些是原子操作,哪些不是原子操作护奈,有一個(gè)直觀的印象:
int a = 10; //1 a++; //2 int b=a; //3 a = a+1; //4

上面這四個(gè)語(yǔ)句中只有第1個(gè)語(yǔ)句是原子操作缔莲,將10賦值給線程工作內(nèi)存的變量a,而語(yǔ)句2(a++),實(shí)際上包含了三個(gè)操作:1. 讀取變量a的值霉旗;2:對(duì)a進(jìn)行加一的操作痴奏;3.將計(jì)算后的值再賦值給變量a,而這三個(gè)操作無(wú)法構(gòu)成原子操作厌秒。對(duì)語(yǔ)句3,4的分析同理可得這兩條語(yǔ)句不具備原子性读拆。當(dāng)然,java內(nèi)存模型中定義了8中操作都是原子的鸵闪,不可再分的檐晕。

  1. lock(鎖定):作用于主內(nèi)存中的變量,它把一個(gè)變量標(biāo)識(shí)為一個(gè)線程獨(dú)占的狀態(tài)蚌讼;
  2. unlock(解鎖):作用于主內(nèi)存中的變量辟灰,它把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái),釋放后的變量才可以被其他線程鎖定
  3. read(讀却凼):作用于主內(nèi)存的變量芥喇,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便后面的load動(dòng)作使用凰萨;
  4. load(載入):作用于工作內(nèi)存中的變量继控,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存中的變量副本
  5. use(使用):作用于工作內(nèi)存中的變量械馆,它把工作內(nèi)存中一個(gè)變量的值傳遞給執(zhí)行引擎,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的值的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作武通;
  6. assign(賦值):作用于工作內(nèi)存中的變量狱杰,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作厅须;
  7. store(存儲(chǔ)):作用于工作內(nèi)存的變量仿畸,它把工作內(nèi)存中一個(gè)變量的值傳送給主內(nèi)存中以便隨后的write操作使用;
  8. write(操作):作用于主內(nèi)存的變量朗和,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中错沽。

上面的這些指令操作是相當(dāng)?shù)讓拥模梢宰鳛閿U(kuò)展知識(shí)面掌握下眶拉。那么如何理解這些指令了?比如千埃,把一個(gè)變量從主內(nèi)存中復(fù)制到工作內(nèi)存中就需要執(zhí)行read,load操作,將工作內(nèi)存同步到主內(nèi)存中就需要執(zhí)行store,write操作忆植。注意的是:java內(nèi)存模型只是要求上述兩個(gè)操作是順序執(zhí)行的并不是連續(xù)執(zhí)行的放可。也就是說(shuō)read和load之間可以插入其他指令,store和writer可以插入其他指令朝刊。比如對(duì)主內(nèi)存中的a,b進(jìn)行訪問(wèn)就可以出現(xiàn)這樣的操作順序:read a,read b, load b,load a耀里。

由原子性變量操作read,load,use,assign,store,write,可以大致認(rèn)為基本數(shù)據(jù)類(lèi)型的訪問(wèn)讀寫(xiě)具備原子性(例外就是long和double的非原子性協(xié)定)

synchronized

上面一共有八條原子操作拾氓,其中六條可以滿足基本數(shù)據(jù)類(lèi)型的訪問(wèn)讀寫(xiě)具備原子性冯挎,還剩下lock和unlock兩條原子操作。如果我們需要更大范圍的原子性操作就可以使用lock和unlock原子操作咙鞍。盡管jvm沒(méi)有把lock和unlock開(kāi)放給我們使用房官,但jvm以更高層次的指令monitorenter和monitorexit指令開(kāi)放給我們使用,反應(yīng)到j(luò)ava代碼中就是---synchronized關(guān)鍵字续滋,也就是說(shuō)synchronized滿足原子性翰守。

volatile
我們先來(lái)看這樣一個(gè)例子:

public class VolatileExample {
    private static volatile int counter = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; I++)
                        counter++;
                }
            });
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(counter);
    }
}

開(kāi)啟10個(gè)線程,每個(gè)線程都自加10000次疲酌,如果不出現(xiàn)線程安全的問(wèn)題最終的結(jié)果應(yīng)該就是:10*10000 = 100000;可是運(yùn)行多次都是小于100000的結(jié)果蜡峰,問(wèn)題在于 volatile并不能保證原子性,在前面說(shuō)過(guò)counter++這并不是一個(gè)原子操作徐勃,包含了三個(gè)步驟:1.讀取變量counter的值事示;2.對(duì)counter加一;3.將新值賦值給變量counter僻肖。如果線程A讀取counter到工作內(nèi)存后肖爵,其他線程對(duì)這個(gè)值已經(jīng)做了自增操作后,那么線程A的這個(gè)值自然而然就是一個(gè)過(guò)期的值臀脏,因此劝堪,總結(jié)果必然會(huì)是小于100000的冀自。

如果讓volatile保證原子性,必須符合以下兩條規(guī)則:

  1. 運(yùn)算結(jié)果并不依賴于變量的當(dāng)前值秒啦,或者能夠確保只有一個(gè)線程修改變量的值熬粗;
  2. 變量不需要與其他的狀態(tài)變量共同參與不變約束 .

競(jìng)態(tài)條件

競(jìng)態(tài)條件(Race Condition):計(jì)算的正確性取決于多個(gè)線程的交替執(zhí)行時(shí)序時(shí),就會(huì)發(fā)生競(jìng)態(tài)條件余境。

最常見(jiàn)的競(jìng)態(tài)條件為:

一驻呐,先檢測(cè)后執(zhí)行。執(zhí)行依賴于檢測(cè)的結(jié)果芳来,而檢測(cè)結(jié)果依賴于多個(gè)線程的執(zhí)行時(shí)序含末,而多個(gè)線程的執(zhí)行時(shí)序通常情況下是不固定不可判斷的,從而導(dǎo)致執(zhí)行結(jié)果出現(xiàn)各種問(wèn)題即舌。
競(jìng)態(tài)條件(Race Condition):計(jì)算的正確性取決于多個(gè)線程的交替執(zhí)行時(shí)序時(shí)佣盒,就會(huì)發(fā)生競(jìng)態(tài)條件。

最常見(jiàn)的競(jìng)態(tài)條件為:

一顽聂,先檢測(cè)后執(zhí)行肥惭。執(zhí)行依賴于檢測(cè)的結(jié)果,而檢測(cè)結(jié)果依賴于多個(gè)線程的執(zhí)行時(shí)序紊搪,而多個(gè)線程的執(zhí)行時(shí)序通常情況下是不固定不可判斷的蜜葱,從而導(dǎo)致執(zhí)行結(jié)果出現(xiàn)各種問(wèn)題。

image

對(duì)于main線程嗦明,如果文件a不存在笼沥,則創(chuàng)建文件a蚪燕,但是在判斷文件a不存在之后娶牌,Task線程創(chuàng)建了文件a,這時(shí)候先前的判斷結(jié)果已經(jīng)失效馆纳,(main線程的執(zhí)行依賴了一個(gè)錯(cuò)誤的判斷結(jié)果)此時(shí)文件a已經(jīng)存在了诗良,但是main線程還是會(huì)繼續(xù)創(chuàng)建文件a,導(dǎo)致Task線程創(chuàng)建的文件a被覆蓋鲁驶、文件中的內(nèi)容丟失等等問(wèn)題鉴裹。

多線程環(huán)境中對(duì)同一個(gè)文件的操作要加鎖。

二钥弯,延遲初始化(最典型即為單例)

public class ObjFactory {
private Obj instance;

public Obj getInstance(){
if(instance == null){
instance = new Obj();
}
return instance;
}
}

image

線程a和線程b同時(shí)執(zhí)行g(shù)etInstance()径荔,線程a看到instance為空,創(chuàng)建了一個(gè)新的Obj對(duì)象脆霎,此時(shí)線程b也需要判斷instance是否為空总处,此時(shí)的instance是否為空取決于不可預(yù)測(cè)的時(shí)序:包括線程a創(chuàng)建Obj對(duì)象需要多長(zhǎng)時(shí)間以及線程的調(diào)度方式,如果b檢測(cè)時(shí)睛蛛,instance為空鹦马,那么b也會(huì)創(chuàng)建一個(gè)instance對(duì)象

復(fù)合操作

實(shí)際情況中胧谈,應(yīng)盡可能使用現(xiàn)有的線程安全對(duì)象來(lái)管理類(lèi)的狀態(tài)。與非線程安全的對(duì)象相比荸频,判斷安全對(duì)象的可能狀態(tài)及其狀態(tài)換情況要更為容易菱肖,從而也更容易維護(hù)和驗(yàn)證線程安全性。
(例如AcomicLong) java.util.concurrent.atomic

一個(gè)類(lèi)中很多安全的狀態(tài)變量旭从,并不一定線程安全稳强。
要保持狀態(tài)的一致性,就需要在單個(gè)原子操作中更新所有相關(guān)的狀態(tài)變量和悦。

內(nèi)置鎖

同步代碼塊:
一個(gè)作為鎖的對(duì)象引
一個(gè)作為由這個(gè)鎖保護(hù)的代碼塊
以關(guān)鍵字synchronized來(lái)修飾的方法就是一種橫跨整個(gè)方法體的同步代碼塊键袱,其中該同步代碼塊的鎖就是方法調(diào)用所在的對(duì)象。靜態(tài)的synchronized方法以Class對(duì)象作為鎖摹闽。

synchronized (lock){
//訪問(wèn)或修改由鎖保護(hù)的共享狀態(tài)
}

每個(gè)java對(duì)象都可以用作一個(gè)實(shí)現(xiàn)同步的鎖蹄咖,這些鎖被稱為內(nèi)置鎖或監(jiān)視器鎖。線程在進(jìn)入同步代碼塊之前會(huì)自動(dòng)獲得鎖付鹿,并且在退出同步代碼塊時(shí)自動(dòng)釋放鎖澜汤,而無(wú)論通過(guò)正常的控制途徑退出,還是通過(guò)從代碼塊中拋出異常退出舵匾,獲得內(nèi)置鎖唯一的途徑就是進(jìn)入這個(gè)鎖保護(hù)的同步代碼塊或同步方法俊抵。
java的內(nèi)置鎖相當(dāng)于一種互斥體(互斥鎖),這意味著最多只有一個(gè)線程能持有這種鎖坐梯,當(dāng)線程A嘗試獲取一個(gè)線程b持有的鎖時(shí)徽诲,線程A必須等待或者阻塞,知道線程b釋放這個(gè)鎖吵血。如果b永遠(yuǎn)不釋放鎖谎替,那么A也將永遠(yuǎn)的等待下去。
由于每次只能有一個(gè)線程執(zhí)行內(nèi)置鎖所保護(hù)的代碼蹋辅,因此钱贯,由這個(gè)鎖保護(hù)的同步代碼塊會(huì)以原子方式執(zhí)行,多個(gè)線程在執(zhí)行該代碼塊時(shí)也不會(huì)相互干擾侦另。并發(fā)環(huán)境中的原子性與事物應(yīng)用程序中原子性有著相同的含義- 一組語(yǔ)句作為一個(gè)不可分割的單元被執(zhí)行秩命,任何一個(gè)執(zhí)行同步代碼塊的線程,都不可能看到有其他線程正在執(zhí)行由同一個(gè)鎖保護(hù)的同步代碼塊褒傅。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弃锐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子殿托,更是在濱河造成了極大的恐慌霹菊,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碌尔,死亡現(xiàn)場(chǎng)離奇詭異浇辜,居然都是意外死亡券敌,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)柳洋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)待诅,“玉大人,你說(shuō)我怎么就攤上這事熊镣”把悖” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵绪囱,是天一觀的道長(zhǎng)测蹲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鬼吵,這世上最難降的妖魔是什么扣甲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮齿椅,結(jié)果婚禮上琉挖,老公的妹妹穿的比我還像新娘。我一直安慰自己涣脚,他們只是感情好示辈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著遣蚀,像睡著了一般矾麻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芭梯,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天险耀,我揣著相機(jī)與錄音,去河邊找鬼粥帚。 笑死胰耗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芒涡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼卖漫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼费尽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起羊始,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤旱幼,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后突委,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體柏卤,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冬三,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缘缚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勾笆。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖桥滨,靈堂內(nèi)的尸體忽然破棺而出窝爪,到底是詐尸還是另有隱情,我是刑警寧澤齐媒,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布蒲每,位于F島的核電站,受9級(jí)特大地震影響喻括,放射性物質(zhì)發(fā)生泄漏邀杏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一唬血、第九天 我趴在偏房一處隱蔽的房頂上張望淮阐。 院中可真熱鬧,春花似錦刁品、人聲如沸泣特。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)状您。三九已至,卻和暖如春兜挨,著一層夾襖步出監(jiān)牢的瞬間膏孟,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工拌汇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柒桑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓噪舀,卻偏偏與公主長(zhǎng)得像魁淳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子与倡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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