好習(xí)慣要堅(jiān)持,這是我第二篇博文,任務(wù)略重,但是要堅(jiān)持努力?。?!
1.競(jìng)爭(zhēng)條件
首先,我們回顧一下《Java核心技術(shù)卷》里講到的多線程的“競(jìng)爭(zhēng)條件”。由于各線程訪問(wèn)數(shù)據(jù)的次序,可能會(huì)產(chǎn)生訛誤的現(xiàn)象,這樣一個(gè)情況通常稱(chēng)為“競(jìng)爭(zhēng)條件”。
那么,訛誤具體是怎么產(chǎn)生的呢?本質(zhì)上,是由于操作的非原子性。比如,假定兩個(gè)線程同時(shí)執(zhí)行指令 account[to] += amount;該指令可能會(huì)被處理如下:
1)將account[to]加載到寄存器。
2)增加amount[to]。
3)將結(jié)果寫(xiě)回account[to]。
現(xiàn)在,假定第一個(gè)線程執(zhí)行步驟1和2,然后,它被剝奪了運(yùn)行權(quán)。假定第二個(gè)線程被喚醒并修改了accounts數(shù)組中的同一項(xiàng)。然后,第1個(gè)線程被喚醒并完成第3步。這樣,這一動(dòng)作擦去了第二個(gè)線程所做的更新。于是,總金額不再正確。
---------------------------------------------我是分割線---------------------------------------------------------------------------------------------
好,我們?cè)購(gòu)膉ava的內(nèi)存模型來(lái)深層次講講“訛誤”,這里有個(gè)概念叫做“緩存一致性”。
大家都知道,計(jì)算機(jī)在執(zhí)行程序時(shí),每條指令都是在CPU中執(zhí)行的,而執(zhí)行指令過(guò)程中,勢(shì)必涉及到數(shù)據(jù)的讀取和寫(xiě)入。由于程序運(yùn)行過(guò)程中的臨時(shí)數(shù)據(jù)是存放在主存(物理內(nèi)存)當(dāng)中的,這時(shí)就存在一個(gè)問(wèn)題,由于CPU執(zhí)行速度很快,而從內(nèi)存讀取數(shù)據(jù)和向內(nèi)存寫(xiě)入數(shù)據(jù)的過(guò)程跟CPU執(zhí)行指令的速度比起來(lái)要慢的多,因此如果任何時(shí)候?qū)?shù)據(jù)的操作都要通過(guò)和內(nèi)存的交互來(lái)進(jìn)行,會(huì)大大降低指令執(zhí)行的速度。因此在CPU里面就有了高速緩存。
也就是,當(dāng)程序在運(yùn)行過(guò)程中,會(huì)將運(yùn)算需要的數(shù)據(jù)從主存復(fù)制一份到CPU的高速緩存當(dāng)中,那么CPU進(jìn)行計(jì)算時(shí)就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫(xiě)入數(shù)據(jù),當(dāng)運(yùn)算結(jié)束之后,再將高速緩存中的數(shù)據(jù)刷新到主存當(dāng)中。舉個(gè)簡(jiǎn)單的例子,比如下面的這段代碼:
i = i + 1;