java之volatile并發(fā)
大?。?/span>0.7 MB 人氣: 2017-09-27 需要積分:0
二。并發(fā)編程中的三個概念
三.Java內(nèi)存模型
四。.深入剖析volatile關鍵字
五。使用volatile關鍵字的場景
若有不正之處請多多諒解,并歡迎批評指正。
一。內(nèi)存模型的相關概念
大家都知道,計算機在執(zhí)行程序時,每條指令都是在CPU中執(zhí)行的,而執(zhí)行指令過程中,勢必涉及到數(shù)據(jù)的讀取和寫入。由于程序運行過程中的臨時數(shù)據(jù)是存放在主存(物理內(nèi)存)當中的,這時就存在一個問題,由于CPU執(zhí)行速度很快,而從內(nèi)存讀取數(shù)據(jù)和向內(nèi)存寫入數(shù)據(jù)的過程跟CPU執(zhí)行指令的速度比起來要慢的多,因此如果任何時候?qū)?shù)據(jù)的操作都要通過和內(nèi)存的交互來進行,會大大降低指令執(zhí)行的速度。因此在CPU里面就有了高速緩存。
也就是,當程序在運行過程中,會將運算需要的數(shù)據(jù)從主存復制一份到CPU的高速緩存當中,那么CPU進行計算時就可以直接從它的高速緩存讀取數(shù)據(jù)和向其中寫入數(shù)據(jù),當運算結束之后,再將高速緩存中的數(shù)據(jù)刷新到主存當中。舉個簡單的例子,比如下面的這段代碼:
i= i+ 1;
當線程執(zhí)行這個語句時,會先從主存當中讀取i的值,然后復制一份到高速緩存當中,然后CPU執(zhí)行指令對i進行加1操作,然后將數(shù)據(jù)寫入高速緩存,最后將高速緩存中i最新的值刷新到主存當中。
這個代碼在單線程中運行是沒有任何問題的,但是在多線程中運行就會有問題了。在多核CPU中,每條線程可能運行于不同的CPU中,因此每個線程運行時有自己的高速緩存(對單核CPU來說,其實也會出現(xiàn)這種問題,只不過是以線程調(diào)度的形式來分別執(zhí)行的)。本文我們以多核CPU為例。
比如同時有2個線程執(zhí)行這段代碼,假如初始時i的值為0,那么我們希望兩個線程執(zhí)行完之后i的值變?yōu)?。但是事實會是這樣嗎?
可能存在下面一種情況:初始時,兩個線程分別讀取i的值存入各自所在的CPU的高速緩存當中,然后線程1進行加1操作,然后把i的最新值1寫入到內(nèi)存。此時線程2的高速緩存當中i的值還是0,進行加1操作之后,i的值為1,然后線程2把i的值寫入內(nèi)存。
最終結果i的值是1,而不是2。這就是著名的緩存一致性問題。通常稱這種被多個線程訪問的變量為共享變量。
也就是說,如果一個變量在多個CPU中都存在緩存(一般在多線程編程時才會出現(xiàn)),那么就可能存在緩存不一致的問題。
為了解決緩存不一致性問題,通常來說有以下2種解決方法:
1)通過在總線加LOCK#鎖的方式
2)通過緩存一致性協(xié)議
這2種方式都是硬件層面上提供的方式。
在早期的CPU當中,是通過在總線上加LOCK#鎖的形式來解決緩存不一致的問題。因為CPU和其他部件進行通信都是通過總線來進行的,如果對總線加LOCK#鎖的話,也就是說阻塞了其他CPU對其他部件訪問(如內(nèi)存),從而使得只能有一個CPU能使用這個變量的內(nèi)存。比如上面例子中 如果一個線程在執(zhí)行 i = i +1,如果在執(zhí)行這段代碼的過程中,在總線上發(fā)出了LCOK#鎖的信號,那么只有等待這段代碼完全執(zhí)行完畢之后,其他CPU才能從變量i所在的內(nèi)存讀取變量,然后進行相應的操作。這樣就解決了緩存不一致的問題。
但是上面的方式會有一個問題,由于在鎖住總線期間,其他CPU無法訪問內(nèi)存,導致效率低下。
所以就出現(xiàn)了緩存一致性協(xié)議。最出名的就是Intel 的MESI協(xié)議,MESI協(xié)議保證了每個緩存中使用的共享變量的副本是一致的。它核心的思想是:當CPU寫數(shù)據(jù)時,如果發(fā)現(xiàn)操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會發(fā)出信號通知其他CPU將該變量的緩存行置為無效狀態(tài),因此當其他CPU需要讀取這個變量時,發(fā)現(xiàn)自己緩存中緩存該變量的緩存行是無效的,那么它就會從內(nèi)存重新讀取。
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%