友情提示:作為一個java小白最近在看java多線程知識,東西還是比較多咬像,推薦大家去看《Java多線程編程指南》算撮,怕自己忘了,所以決定碼些字县昂。
開始之前肮柜,建議大家一定要系統(tǒng)地學(xué)習(xí)一下操作系統(tǒng),并且不能光看網(wǎng)上碎片化的知識點(diǎn)倒彰,所以一點(diǎn)一點(diǎn)來吧审洞。
我準(zhǔn)備先回顧一下非常基礎(chǔ)而且重要的知識點(diǎn)待讳,先從三個特性下手芒澜,原子性,可見性创淡,有序性痴晦。每一點(diǎn)的內(nèi)容都很多,一點(diǎn)一點(diǎn)來琳彩,先介紹原子性誊酌。
原子性:
書本上的定義:對于涉及共享變量訪問的操作部凑,若該操作從其執(zhí)行線程以外的任意線程來看是不可分割的,那么該操作就是原子操作碧浊,相應(yīng)的我們就稱該操作是具有原子性的涂邀。
這句話的豐富含義有:
1.原子操作是對于多線程而言的,對于單一線程箱锐,無所謂原子性比勉。有點(diǎn)多線程常識的朋友這個都應(yīng)該知道,但也要時刻牢記瑞躺。
2.原子操作是針對共享變量的敷搪。因此,涉及局部變量(如方法中的變量)我們是沒必要要求它具有原子性的幢哨,
3.原子操作是不可分割的赡勘。(我們要站在多線程的角度)指訪問某個共享變量的操作從其執(zhí)行線程之外的線程來看,該操作要么已經(jīng)執(zhí)行完畢捞镰,要么尚未發(fā)生闸与,其他線程不會看到執(zhí)行操作的中間結(jié)果。學(xué)過數(shù)據(jù)庫的朋友應(yīng)該很熟悉這種原子性岸售。那么践樱,站在訪問變量的角度,我們可以這樣看凸丸,如果要改變一個對象拷邢,而該對象包含一組需要同時改變的共享變量,那么屎慢,在一個線程開始改變一個變量之后瞭稼,在其它線程看來,這個對象的所有屬性要么都被修改腻惠,要么都沒有被修改环肘,不會看到部分修改的中間結(jié)果。(這只是最簡單的一種解釋集灌,以后我們還會講到i++以及初始化等操作)
好了悔雹,這是我們從書上定義角度出發(fā)得到?jīng)]有任何問題的定義,下面我想說說我對原子性的理解(可能有誤欣喧,歡迎指正)腌零。
首先保持原子性的重要性不言而喻,這可能是我們學(xué)多線程最直觀的感受唆阿。我們需要讓一個共享變量串行的被訪問修改莱没,不能造成不一致性。
這里先說一下互斥性酷鸦,學(xué)過操作系統(tǒng)的同學(xué)課上可能都接觸過生產(chǎn)者消費(fèi)者模型,這個模型里面的共享變量就是緩存區(qū)的大小。我們學(xué)過通過一個互斥變量+臨界區(qū)來控制兩個線程對緩存區(qū)的訪問臼隔。實(shí)現(xiàn)方式大概是:
```
int mutex = 1;
while(true){
? ? wait(mutex);
? ? critical section
? ? signal(mutex);
}
```
上面的這個偽代碼大概就是實(shí)現(xiàn)互斥訪問的機(jī)理嘹裂,wait(mutex)操作相當(dāng)于:
```
while(mutex <= 0);
mutex--;
```
wait()操作的意思就是當(dāng)互斥變量mutex<=0時,就一直阻塞在這里(不停循環(huán))摔握。若mutex>0寄狼,其實(shí)就是等于1,那么將mutex值變?yōu)?氨淌,接著執(zhí)行臨界區(qū)代碼泊愧。這就保證其他線程此時想進(jìn)入臨界區(qū)時由于得到mutex為0,就一直阻塞盛正。那么我們肯定還要想辦法把mutex變回1删咱,不然之后線程就進(jìn)入不了臨界區(qū)了。所以豪筝,大家就很容易想到signal(mutex)做的事情了:mutex++;
這里需要注意的是痰滋,我們要與多線程里面的wait(),signal()/notify()區(qū)別一下。上面講的互斥變量其實(shí)就是鎖的原理续崖。同一時間我們只有一個線程能擁有鎖敲街,所以鎖具有的排他性。那么严望,實(shí)現(xiàn)鎖基本有兩種方法:
1.synchronized(內(nèi)部鎖)/Lock(顯示鎖)多艇。
2.CAS(Compare And Swap),這其實(shí)是鎖的底層實(shí)現(xiàn)像吻。之后再細(xì)講這兩塊峻黍。
說的有點(diǎn)多,拉回來萧豆。通過互斥變量(鎖)的特性奸披,我們可以實(shí)現(xiàn)多個線程執(zhí)行代碼到這個區(qū)域的時候必須先獲得許可,而且同一時間只有一個線程可以做到涮雷,所以咱們就實(shí)現(xiàn)了串行化阵面。
OK,到這里我們實(shí)現(xiàn)原子性就很簡單了,只需要把對一組共享變量的操作(或者對一個共享變量的多個原子操作)放進(jìn)臨界區(qū)就可以了洪鸭。這樣样刷,在執(zhí)行線程以外的其他線程看來,臨界區(qū)不管有多少操作都是原子操作览爵。
當(dāng)然了置鼻,在Java里面要實(shí)現(xiàn)這種多線程并發(fā)訪問這遠(yuǎn)遠(yuǎn)不夠的。
我還要強(qiáng)調(diào)一個很重要的東西蜓竹,如何判斷一個操作是不是原子操作呢箕母?
記住储藐,在Java語言中,long型和double型以外的任何類型的變量的寫操作都是原子操作嘶是。(不提讀操作的原因是如果所有線程都是讀操作的話钙勃,那么沒必要保持原子性。我們需要考慮的是read-modify-write和check-then-act這兩種形式)
所以對于基本類型和引用類型的寫操作(不是指初始化)都是本身具有原子性的聂喇。對于long型和double型我們可以簡單理解在32位虛擬機(jī)上辖源,我們先對變量的低32位賦值,再對高32位賦值希太,那么這樣兩個操作就不是原子操作了克饶,我們很可能讀到中間狀態(tài)。處理這種變量我們可以加一個volatile關(guān)鍵字誊辉,之后的文章我會詳述這個關(guān)鍵字矾湃。
還有一個命題:原子操作 + 原子操作 != 原子操作
這里給大家一個很簡單的判別方式芥映,也就是我們賦值語句=的右邊洲尊,(前提是=左邊是對共享變量的操作)一旦出現(xiàn)共享變量了,那么就不是原子操作了奈偏,因?yàn)檫@肯定涉及讀操作和寫操作坞嘀。最容易理解的就是i++(i=i+1)。
最后惊来,要更好得理解原子性丽涩,我們最好去理解一下java內(nèi)存模型,以及讀寫操作的過程裁蚁。這一塊很重要矢渊,大家不要忽略。
寫的第一篇文章枉证,有問題歡迎指正矮男,下一篇給大家介紹 可見性。