你們都知道,計算機在執行程序時,每條指令都是在CPU中執行的,而執行指令過程當中,勢必涉及到數據的讀取和寫入。因爲程序運行過程當中的臨時數據是存放在主存(物理內存)當中的,這時就存在一個問題,因爲CPU執行速度很快,而從內存讀取數據和向內存寫入數據的過程跟CPU比起來要慢的多,所以若是任什麼時候間對數據的操做都要經過和內存的交互來進行,會大大下降指令執行的速度。所以在CPU裏面就有了高速緩存。html
也就是,當程序在運行過程當中,會將運算須要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就能夠直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束後,再將高速緩存中的數據刷新到主存當中。舉個簡單的例子,好比下面的這段代碼:編程
i=i+1 |
當線程執行這個語句時,會先從主存當中讀取i的值,而後複製一份到高速緩存當中,而後CPU執行指令對i進行加1操做,而後將數據寫入高速緩存,最後將高速緩存中i最新的值刷新到主存當中。緩存
這個代碼在單線程中運行是沒有任何問題的,可是在多線程中運行就會有問題了。在多核CPU中,每條線程能夠運行於不一樣的CPU中,所以每一個線程運行時有本身的高速緩存(對單核CPU來講,其實也會出現這種問題。只不過是以線程調度的形式來分別執行的)。本文咱們以多核CU爲例。安全
下圖中,線程1和線程2共享i,並同時對i進行i++操做,可能存在這種狀況,最終結果i的值是1,而不是2。這就是著名的緩存一致性問題。多線程
爲了解決緩存不一致問題,一般來講有如下2種解決方法:框架
這兩種方式都是硬件層面上提供的方式。性能
在早期的CPU當中,是經過在總線加Lock#鎖的形式來解決緩存不一致的問題。由於CPU和其餘部件進行通訊都是經過總線來進行的,若是對總線加Lock#鎖的話,也就是說阻塞了其餘CPU對其餘部件訪問(如內存),從而使得只有一個CPU能使用這個變量的內存。好比上面例子中,若是一個線程在執行i=i+1過程當中,在總線上發出了LOCK#鎖的信號,那麼只有等待這段代碼徹底執行完畢以後,其餘CPU才能從變量i所在的內存讀取變量。這樣就解決了緩存不一致的問題。優化
可是上面的方式會有一個問題,因爲在鎖定總線期間,其餘CPU沒法訪問內存,容易致使如下的Starvation(飢餓)和DeadLock(死鎖)問題。spa
因此就出現了緩存一致性協議。最出名的就是Intel的MESI協議,MESI協議保證了每一個緩存中使用的共享變量的副本是一致的。它的核心思想是:當CPU寫數據時,若是發現操做的變量是共享變量,即在其餘CPU中也存在該變量的副本,會發出信號通知其餘CPU將該變量的緩存行置爲無效狀態,所以當其餘CPU須要讀取這個變量時,發現本身緩存中緩存該變量的緩存行是無效的,那麼它就會從內存中從新讀取。線程
線程1鎖定A,爾後嘗試鎖定B,線程2鎖定B,爾後嘗試鎖定A,形成兩個線程同時等待對方釋放鎖,進入死鎖狀態。
總結:多線程編程容易產生問題的根本緣由在於鎖
不用切換至內核態,可能會更高效
不佔用CPU資源
會切換至內核態
你只能在有限的一些情形下使用volatile變量替代鎖。要是volatile變量提供理想的線程安全,必須同時知足下面兩個條件:
實際上,這些條件代表,volatile變量不能用做線程安全計數器。雖然增量操做(x++)看起來相似一個單獨操做,實際上它是一個有讀取-修改-寫入操做序列組成的組合操做,必須以原子方式執行,而volatile不能提供必須的原子性操做。
簡單局其中使用一例:狀態標誌
也許實現volatile變量的規範使用僅僅是使用一個boolean狀態標誌,用於指示發生了一個重要的一次性時間,例如完成初始化或請求停機。
不少應用程序包含了一種控制結構,形式爲"在尚未準備好中止程序時再執行一些工做"。如如下代碼所示,將volatile變量做爲狀態標誌使用:
volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { |
極可能會從循環外部調用shutdown()方法 -- 即在另外一個線程中 -- 所以,須要執行某種同步來確保正確實現shutdownRequested變量的可見性。相對於synchronized,這裏很是適合使用volatile。
總結:
那麼如何解決starvation的問題?
引入公平鎖
參考:http://www.importnew.com/18126.html
參考:http://www.cnblogs.com/shangxiaofei/p/5564047.html
參考:煉數成金--多線程及容錯性處理