volatile關鍵字解析

1、內存模型的相關概念

計算機在執行程序時,指令的執行都是在CPU中。在執行指令的過程當中,會將臨時變量存儲在主存(物理內存)中。 CPU執行指令很快,而從內存中讀取和寫入數據卻相對來講很慢,因此就有了高速緩存。 在程序運行時,先將數據從主存複製一份到高速緩存中,而後CPU執行指令時,就能夠直接從高速緩存中讀取和寫入數據,而後刷新到內存中。編程

這裏就產生了一個問題。 好比i是一個共享變量,初始值爲0緩存

i = i+1;

在單線程中,CPU執行指令時,先從高速緩存中取出數據,而後進行加1操做,再寫入高速緩存中,而後刷新到主存中。 可是多線程中,每個線程都有本身的工做緩存,線程A,B從高速緩存中讀取到數據i的值都爲0,在進行加1操做後,寫入本身的工做緩存中,再刷新到內存中。 進行兩次加法操做,值卻爲1。多線程

這就是緩存不一致問題。併發

解決緩存不一致有兩個解決方法。優化

1.在總線加入LOCK#鎖線程

2.使用緩存一致性協議code

總線加入鎖來阻塞其餘線程訪問時,會致使線程阻塞,使效率低下。排序

緩存一致性協議的核心思想是:當線程對一個共享變量進行寫操做時,會發出通知,讓其餘的線程該變量的緩存行置爲無效,其餘線程操做該變量時,會從新從內存中讀取數據。內存

2、JAVA內存模型

Java內存模型規定全部的變量都存在主存中,每一個線程都有本身的工做內存(高速緩存),線程的全部操做都只能操做工做內存,不能操做主存,也不能操做其餘線程的工做內存。it

3、併發編程中的三個概念

1.原子性

一個操做或多個操做,所有執行不會被其餘操做打斷。 原子性只針對讀取和寫入指令。 好比

i = i + 1;

這個代碼是沒有原子性的,先從高速緩存中取出i的數據,而後進行加1操做,最後寫入高速緩存,再刷新到內存中。

若是要保證這個操做的原子性,就能夠經過synchronized和lock來實現。

2.可見性

多個線程訪問同一個變量時,一個線程修改了變量的值,其餘線程能當即看到修改的值。

Java提供了volatile來保證有效性。

當一個共享變量被volatile修飾時,它保證當線程修改它的值時當即被更新到主存中,其餘線程讀取時,從主存中讀取值。

3.有序性

程序執行順序按照代碼的前後順序執行。

處理器爲了提升程序運行效率,可能會對代碼執行順序進行優化。這裏可能會發生指令重排序。

好比兩個賦值語句

context = init()
boolean flag = true;

處理器在執行時,可能先執行flag = true,再執行初始化。

多線程執行時,若是後面有根據flag爲真,對初始化內容的操做。線程1先執行flag=true,還未初始化內容,線程2根據flag爲真執行了對內容的操做,就會致使異常。

while(!flag){
    sleep()
}
dosomething(context);

能夠用synchronized和look保證每一個時刻只有一個線程訪問代碼塊,至關於順序執行代碼。

4、深刻剖析volatile關鍵字

  1. 保證不一樣線程對這個變量具備可見性,即一個線程修改了某個變量的值,這個新值對其餘線程是當即可見的。
  2. 被volatile修飾的變量禁止指令重排序

多個線程對一個volatile修飾的值進行操做時,可能會致使原子性不一致的問題。 由於volatile只保證了可見性和有序性。

對於自增代碼

i++;

因爲該操做不是原子性,因此可能存在一個線程從內存中取出值,以後另外一個線程對該數據取值操做寫入內存。由於可見性是指讀操做,以前線程的值已經取出來了,不會變,以後操做獲得的值就是1,而不是2。

解決方法是該自增代碼加Lock或synchronized

相關文章
相關標籤/搜索