Java 并發(fā)編程之核心理論

并發(fā)編程是Java程序員最重要的技能之一页衙,也是最難掌握的一種技能蓄氧。它要求編程者對(duì)計(jì)算機(jī)最底層的運(yùn)作原理有深刻的理解函似,同時(shí)要求編程者邏輯清晰、思維縝密喉童,這樣才能寫出高效撇寞、安全、可靠的多線程并發(fā)程序堂氯。

本系列會(huì)從線程間協(xié)調(diào)的方式(wait蔑担、notify、notifyAll)咽白、Synchronized及Volatile的本質(zhì)入手啤握,詳細(xì)解釋JDK為我們提供的每種并發(fā)工具和底層實(shí)現(xiàn)機(jī)制。

在此基礎(chǔ)上晶框,我們會(huì)進(jìn)一步分析java.util.concurrent包的工具類排抬,包括其使用方式、實(shí)現(xiàn)源碼及其背后的原理授段。本文是該系列的第一篇文章蹲蒲,是這系列中最核心的理論部分,之后的文章都會(huì)以此為基礎(chǔ)來分析和解釋侵贵。

共享性

數(shù)據(jù)共享性是線程安全的主要原因之一届搁。如果所有的數(shù)據(jù)只是在線程內(nèi)有效,那就不存在線程安全性問題窍育,這也是我們?cè)诰幊痰臅r(shí)候經(jīng)常不需要考慮線程安全的主要原因之一咖祭。

但是,在多線程編程中蔫骂,數(shù)據(jù)共享是不可避免的。最典型的場(chǎng)景是數(shù)據(jù)庫中的數(shù)據(jù)牺汤,為了保證數(shù)據(jù)的一致性辽旋,我們通常需要共享同一個(gè)數(shù)據(jù)庫中數(shù)據(jù),即使是在主從的情況下檐迟,訪問的也同一份數(shù)據(jù)补胚,主從只是為了訪問的效率和數(shù)據(jù)安全,而對(duì)同一份數(shù)據(jù)做的副本追迟。我們現(xiàn)在溶其,通過一個(gè)簡單的示例來演示多線程下共享數(shù)據(jù)導(dǎo)致的問題:

代碼段一:

上述代碼的目的是對(duì)count進(jìn)行加一操作,執(zhí)行1000次敦间,不過這里是通過10個(gè)線程來實(shí)現(xiàn)的瓶逃,每個(gè)線程執(zhí)行100次束铭,正常情況下,應(yīng)該輸出1000厢绝。不過契沫,如果你運(yùn)行上面的程序,你會(huì)發(fā)現(xiàn)結(jié)果卻不是這樣昔汉。下面是某次的執(zhí)行結(jié)果(每次運(yùn)行的結(jié)果不一定相同懈万,有時(shí)候也可能獲取到正確的結(jié)果):

小編是一個(gè)有著5年工作經(jīng)驗(yàn)的java程序員,對(duì)于java靶病,自己有做資料的整合会通,一個(gè)完整學(xué)習(xí)java的路線,學(xué)習(xí)資料和工具娄周,相信這里有很多學(xué)習(xí)java的小伙伴涕侈,我創(chuàng)立了一個(gè)2000人學(xué)習(xí)扣群,479121291昆咽。每晚都有java的直播課程驾凶。無論是初級(jí)還是進(jìn)階的小伙伴小編我都?xì)g迎!

可以看出掷酗,對(duì)共享變量操作调违,在多線程環(huán)境下很容易出現(xiàn)各種意想不到的的結(jié)果。

互斥性

資源互斥是指同時(shí)只允許一個(gè)訪問者對(duì)其進(jìn)行訪問泻轰,具有唯一性和排它性技肩。我們通常允許多個(gè)線程同時(shí)對(duì)數(shù)據(jù)進(jìn)行讀操作,但同一時(shí)間內(nèi)只允許一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行寫操作浮声。所以我們通常將鎖分為共享鎖和排它鎖虚婿,也叫做讀鎖和寫鎖。

如果資源不具有互斥性泳挥,即使是共享資源然痊,我們也不需要擔(dān)心線程安全。例如屉符,對(duì)于不可變的數(shù)據(jù)共享剧浸,所有線程都只能對(duì)其進(jìn)行讀操作,所以不用考慮線程安全問題矗钟。但是對(duì)共享數(shù)據(jù)的寫操作唆香,一般就需要保證互斥性,上述例子中就是因?yàn)闆]有保證互斥性才導(dǎo)致數(shù)據(jù)的修改產(chǎn)生問題吨艇。

Java?中提供多種機(jī)制來保證互斥性躬它,最簡單的方式是使用Synchronized。現(xiàn)在我們?cè)谏厦娉绦蛑屑由蟂ynchronized再執(zhí)行:

代碼段二:

現(xiàn)在再執(zhí)行上述代碼东涡,會(huì)發(fā)現(xiàn)無論執(zhí)行多少次冯吓,返回的最終結(jié)果都是1000倘待。

原子性

原子性就是指對(duì)數(shù)據(jù)的操作是一個(gè)獨(dú)立的、不可分割的整體桑谍。換句話說延柠,就是一次操作,是一個(gè)連續(xù)不可中斷的過程锣披,數(shù)據(jù)不會(huì)執(zhí)行到一半的時(shí)候被其他線程所修改贞间。

保證原子性的最簡單方式是操作系統(tǒng)指令,就是說如果一次操作對(duì)應(yīng)一條操作系統(tǒng)指令雹仿,這樣肯定可以保證原子性增热。但是很多操作不能通過一條指令就完成。例如胧辽,對(duì)long類型的運(yùn)算峻仇,很多系統(tǒng)就需要分成多條指令分別對(duì)高位和低位進(jìn)行操作才能完成。還比如邑商,我們經(jīng)常使用的整數(shù)?i++的操作摄咆,其實(shí)需要分成三個(gè)步驟:(1)讀取整數(shù)?i?的值;(2)對(duì)?i?進(jìn)行加一操作人断;(3)將結(jié)果寫回內(nèi)存吭从。這個(gè)過程在多線程下就可能出現(xiàn)如下現(xiàn)象:

這也是代碼段一執(zhí)行的結(jié)果為什么不正確的原因。對(duì)于這種組合操作恶迈,要保證原子性涩金,最常見的方式是加鎖,如Java中的Synchronized或Lock都可以實(shí)現(xiàn)暇仲,代碼段二就是通過Synchronized實(shí)現(xiàn)的调榄。

除了鎖以外柄错,還有一種方式就是CAS(Compare And Swap),即修改數(shù)據(jù)之前先比較與之前讀取到的值是否一致嘹屯,如果一致帖鸦,則進(jìn)行修改抑诸,如果不一致則重新執(zhí)行谍椅,這也是樂觀鎖的實(shí)現(xiàn)原理趴荸。不過CAS在某些場(chǎng)景下不一定有效,比如另一線程先修改了某個(gè)值中跌,然后再改回原來值,這種情況下菇篡,CAS是無法判斷的漩符。

可見性

要理解可見性,需要先對(duì)JVM的內(nèi)存模型有一定的了解驱还,JVM的內(nèi)存模型與操作系統(tǒng)類似嗜暴,如圖所示:

從這個(gè)圖中我們可以看出凸克,每個(gè)線程都有一個(gè)自己的工作內(nèi)存(相當(dāng)于CPU高級(jí)緩沖區(qū),這么做的目的還是在于進(jìn)一步縮小存儲(chǔ)系統(tǒng)與CPU之間速度的差異闷沥,提高性能)萎战,對(duì)于共享變量,線程每次讀取的是工作內(nèi)存中共享變量的副本舆逃,寫入的時(shí)候也直接修改工作內(nèi)存中副本的值蚂维,然后在某個(gè)時(shí)間點(diǎn)上再將工作內(nèi)存與主內(nèi)存中的值進(jìn)行同步。

這樣導(dǎo)致的問題是路狮,如果線程1對(duì)某個(gè)變量進(jìn)行了修改虫啥,線程2卻有可能看不到線程1對(duì)共享變量所做的修改。通過下面這段程序我們可以演示一下不可見的問題:

從直觀上理解奄妨,這段程序應(yīng)該只會(huì)輸出100涂籽,ready的值是不會(huì)打印出來的。實(shí)際上砸抛,如果多次執(zhí)行上面代碼的話评雌,可能會(huì)出現(xiàn)多種不同的結(jié)果,下面是我運(yùn)行出來的某兩次的結(jié)果:

當(dāng)然直焙,這個(gè)結(jié)果也只能說是有可能是可見性造成的景东,當(dāng)寫線程(WriterThread)設(shè)置ready=true后,讀線程(ReaderThread)看不到修改后的結(jié)果箕般,所以會(huì)打印false耐薯,對(duì)于第二個(gè)結(jié)果,也就是執(zhí)行if (!ready)時(shí)還沒有讀取到寫線程的結(jié)果丝里,但執(zhí)行System.out.println(ready)時(shí)讀取到了寫線程執(zhí)行的結(jié)果曲初。

不過,這個(gè)結(jié)果也有可能是線程的交替執(zhí)行所造成的杯聚。Java 中可通過Synchronized或Volatile來保證可見性臼婆,具體細(xì)節(jié)會(huì)在后續(xù)的文章中分析。

有序性

為了提高性能幌绍,編譯器和處理器可能會(huì)對(duì)指令做重排序颁褂。重排序可以分為三種:

(1)編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下傀广,可以重新安排語句的執(zhí)行順序颁独。

(2)指令級(jí)并行的重排序。現(xiàn)代處理器采用了指令級(jí)并行技術(shù)(Instruction-Level Parallelism伪冰, ILP)來將多條指令重疊執(zhí)行誓酒。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。

(3)內(nèi)存系統(tǒng)的重排序靠柑。由于處理器使用緩存和讀/寫緩沖區(qū)寨辩,這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。

我們可以直接參考一下JSR 133 中對(duì)重排序問題的描述:

先看上圖中的(1)源碼部分歼冰,從源碼來看靡狞,要么指令 1 先執(zhí)行要么指令 3先執(zhí)行。如果指令 1 先執(zhí)行隔嫡,r2不應(yīng)該能看到指令 4 中寫入的值甸怕。如果指令 3 先執(zhí)行,r1不應(yīng)該能看到指令 2 寫的值畔勤。但是運(yùn)行結(jié)果卻可能出現(xiàn)r2==2蕾各,r1==1的情況,這就是“重排序”導(dǎo)致的結(jié)果庆揪。

上圖(2)即是一種可能出現(xiàn)的合法的編譯結(jié)果式曲,編譯后,指令1和指令2的順序可能就互換了缸榛。因此吝羞,才會(huì)出現(xiàn)r2==2,r1==1的結(jié)果内颗。Java 中也可通過Synchronized或Volatile來保證順序性钧排。

總結(jié)

本文對(duì)Java 并發(fā)編程中的理論基礎(chǔ)進(jìn)行了講解,有些東西在后續(xù)的分析中還會(huì)做更詳細(xì)的討論均澳,如可見性恨溜、順序性等。后續(xù)的文章都會(huì)以本章內(nèi)容作為理論基礎(chǔ)來討論找前。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末糟袁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躺盛,更是在濱河造成了極大的恐慌项戴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件槽惫,死亡現(xiàn)場(chǎng)離奇詭異周叮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)界斜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門仿耽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人各薇,你說我怎么就攤上這事项贺。” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵敬扛,是天一觀的道長。 經(jīng)常有香客問我朝抖,道長啥箭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任治宣,我火速辦了婚禮急侥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侮邀。我一直安慰自己坏怪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布绊茧。 她就那樣靜靜地躺著铝宵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪华畏。 梳的紋絲不亂的頭發(fā)上鹏秋,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音亡笑,去河邊找鬼侣夷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛仑乌,可吹牛的內(nèi)容都是我干的百拓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼晰甚,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼衙传!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起压汪,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤粪牲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后止剖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腺阳,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年穿香,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亭引。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡皮获,死狀恐怖焙蚓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤购公,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布萌京,位于F島的核電站,受9級(jí)特大地震影響宏浩,放射性物質(zhì)發(fā)生泄漏知残。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一比庄、第九天 我趴在偏房一處隱蔽的房頂上張望求妹。 院中可真熱鬧,春花似錦佳窑、人聲如沸制恍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽净神。三九已至,卻和暖如春溉委,著一層夾襖步出監(jiān)牢的瞬間强挫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工薛躬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俯渤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓型宝,卻偏偏與公主長得像八匠,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趴酣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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