友情提示:作為一個(gè)java小白最近在看java多線程知識(shí),東西還是比較多,推薦大家去看《Java多線程編程指南》,怕自己忘了,所以決定碼些字。
開(kāi)始之前,建議大家一定要系統(tǒng)地學(xué)習(xí)一下操作系統(tǒng),并且不能光看網(wǎng)上碎片化的知識(shí)點(diǎn),所以一點(diǎn)一點(diǎn)來(lái)吧。
我準(zhǔn)備先回顧一下非?;A(chǔ)而且重要的知識(shí)點(diǎn),先從三個(gè)特性下手,原子性,可見(jiàn)性,有序性。每一點(diǎn)的內(nèi)容都很多,一點(diǎn)一點(diǎn)來(lái),先介紹原子性。
原子性:
書(shū)本上的定義:對(duì)于涉及共享變量訪問(wèn)的操作,若該操作從其執(zhí)行線程以外的任意線程來(lái)看是不可分割的,那么該操作就是原子操作,相應(yīng)的我們就稱(chēng)該操作是具有原子性的。
這句話的豐富含義有:
1.原子操作是對(duì)于多線程而言的,對(duì)于單一線程,無(wú)所謂原子性。有點(diǎn)多線程常識(shí)的朋友這個(gè)都應(yīng)該知道,但也要時(shí)刻牢記。
2.原子操作是針對(duì)共享變量的。因此,涉及局部變量(如方法中的變量)我們是沒(méi)必要要求它具有原子性的,
3.原子操作是不可分割的。(我們要站在多線程的角度)指訪問(wèn)某個(gè)共享變量的操作從其執(zhí)行線程之外的線程來(lái)看,該操作要么已經(jīng)執(zhí)行完畢,要么尚未發(fā)生,其他線程不會(huì)看到執(zhí)行操作的中間結(jié)果。學(xué)過(guò)數(shù)據(jù)庫(kù)的朋友應(yīng)該很熟悉這種原子性。那么,站在訪問(wèn)變量的角度,我們可以這樣看,如果要改變一個(gè)對(duì)象,而該對(duì)象包含一組需要同時(shí)改變的共享變量,那么,在一個(gè)線程開(kāi)始改變一個(gè)變量之后,在其它線程看來(lái),這個(gè)對(duì)象的所有屬性要么都被修改,要么都沒(méi)有被修改,不會(huì)看到部分修改的中間結(jié)果。(這只是最簡(jiǎn)單的一種解釋?zhuān)院笪覀冞€會(huì)講到i++以及初始化等操作)
好了,這是我們從書(shū)上定義角度出發(fā)得到?jīng)]有任何問(wèn)題的定義,下面我想說(shuō)說(shuō)我對(duì)原子性的理解(可能有誤,歡迎指正)。
首先保持原子性的重要性不言而喻,這可能是我們學(xué)多線程最直觀的感受。我們需要讓一個(gè)共享變量串行的被訪問(wèn)修改,不能造成不一致性。
這里先說(shuō)一下互斥性,學(xué)過(guò)操作系統(tǒng)的同學(xué)課上可能都接觸過(guò)生產(chǎn)者消費(fèi)者模型,這個(gè)模型里面的共享變量就是緩存區(qū)的大小。我們學(xué)過(guò)通過(guò)一個(gè)互斥變量+臨界區(qū)來(lái)控制兩個(gè)線程對(duì)緩存區(qū)的訪問(wèn)。實(shí)現(xiàn)方式大概是:
```
int mutex = 1;
while(true){
? ? wait(mutex);
? ? critical section
? ? signal(mutex);
}
```
上面的這個(gè)偽代碼大概就是實(shí)現(xiàn)互斥訪問(wèn)的機(jī)理,wait(mutex)操作相當(dāng)于:
```
while(mutex <= 0);
mutex--;
```
wait()操作的意思就是當(dāng)互斥變量mutex<=0時(shí),就一直阻塞在這里(不停循環(huán))。若mutex>0,其實(shí)就是等于1,那么將mutex值變?yōu)?,接著執(zhí)行臨界區(qū)代碼。這就保證其他線程此時(shí)想進(jìn)入臨界區(qū)時(shí)由于得到mutex為0,就一直阻塞。那么我們肯定還要想辦法把mutex變回1,不然之后線程就進(jìn)入不了臨界區(qū)了。所以,大家就很容易想到signal(mutex)做的事情了:mutex++;
這里需要注意的是,我們要與多線程里面的wait(),signal()/notify()區(qū)別一下。上面講的互斥變量其實(shí)就是鎖的原理。同一時(shí)間我們只有一個(gè)線程能擁有鎖,所以鎖具有的排他性。那么,實(shí)現(xiàn)鎖基本有兩種方法:
1.synchronized(內(nèi)部鎖)/Lock(顯示鎖)。
2.CAS(Compare And Swap),這其實(shí)是鎖的底層實(shí)現(xiàn)。之后再細(xì)講這兩塊。
說(shuō)的有點(diǎn)多,拉回來(lái)。通過(guò)互斥變量(鎖)的特性,我們可以實(shí)現(xiàn)多個(gè)線程執(zhí)行代碼到這個(gè)區(qū)域的時(shí)候必須先獲得許可,而且同一時(shí)間只有一個(gè)線程可以做到,所以咱們就實(shí)現(xiàn)了串行化。
OK,到這里我們實(shí)現(xiàn)原子性就很簡(jiǎn)單了,只需要把對(duì)一組共享變量的操作(或者對(duì)一個(gè)共享變量的多個(gè)原子操作)放進(jìn)臨界區(qū)就可以了。這樣,在執(zhí)行線程以外的其他線程看來(lái),臨界區(qū)不管有多少操作都是原子操作。
當(dāng)然了,在Java里面要實(shí)現(xiàn)這種多線程并發(fā)訪問(wèn)這遠(yuǎn)遠(yuǎn)不夠的。
我還要強(qiáng)調(diào)一個(gè)很重要的東西,如何判斷一個(gè)操作是不是原子操作呢?
記住,在Java語(yǔ)言中,long型和double型以外的任何類(lèi)型的變量的寫(xiě)操作都是原子操作。(不提讀操作的原因是如果所有線程都是讀操作的話,那么沒(méi)必要保持原子性。我們需要考慮的是read-modify-write和check-then-act這兩種形式)
所以對(duì)于基本類(lèi)型和引用類(lèi)型的寫(xiě)操作(不是指初始化)都是本身具有原子性的。對(duì)于long型和double型我們可以簡(jiǎn)單理解在32位虛擬機(jī)上,我們先對(duì)變量的低32位賦值,再對(duì)高32位賦值,那么這樣兩個(gè)操作就不是原子操作了,我們很可能讀到中間狀態(tài)。處理這種變量我們可以加一個(gè)volatile關(guān)鍵字,之后的文章我會(huì)詳述這個(gè)關(guān)鍵字。
還有一個(gè)命題:原子操作 + 原子操作 != 原子操作
這里給大家一個(gè)很簡(jiǎn)單的判別方式,也就是我們賦值語(yǔ)句=的右邊,(前提是=左邊是對(duì)共享變量的操作)一旦出現(xiàn)共享變量了,那么就不是原子操作了,因?yàn)檫@肯定涉及讀操作和寫(xiě)操作。最容易理解的就是i++(i=i+1)。
最后,要更好得理解原子性,我們最好去理解一下java內(nèi)存模型,以及讀寫(xiě)操作的過(guò)程。這一塊很重要,大家不要忽略。
寫(xiě)的第一篇文章,有問(wèn)題歡迎指正,下一篇給大家介紹 可見(jiàn)性。