volatile和synchronized關鍵字
synchronized
java課上講到過synchronized
首先看看用synchronized和沒用synchronized的區別
import lombok.Getter; /** * @author yintianhao * @createTime 20190123 16:28 * @description synchronized */ public class SYN { @Getter private int count; private Object lock; public SYN(){ count = 0; lockView Code= new Object(); } public void noSyn(){ for (int i = 0;i < 100;i++){ count++; } } public void Syn(){ synchronized (lock){ for (int i = 0;i < 100;i++){ count++; } } } }
要進行的操作是對count進行自增操作,執行一次裏面的Syn和noSyn函數都是對count這個變量進行加一百次的操作
不同的就是,Syn函數用了synchronized
然後開啟十個線程來執行這兩個函數,之後打印出count的值,我們期望的是count等於1000
List<Thread> threads = new ArrayList<>(); SYN syn = new SYN(); for (int i = 0;i < 10;i++){ threads.add(new Thread(()->syn.noSyn(),"thread-"+i)); } for (Thread o:threads) o.start();View Codefor (Thread o:threads) o.join(); System.out.println(syn.getCount());
再看運行結果
可是結果卻不是1000
然後再把運行的函數改成Syn(),再次看結果
1000符合預期,為了避免偶然,重復運行幾次結果仍是1000
那麽為什麽這裏結果不同的呢,原因就是牽涉到另外一個概念,鎖,在這個例子中,SYN類中的lock對象就可以看做一把鎖
只有拿到鎖,才能執行synchronized代碼塊中的代碼塊,所以當一個線程在執行synchronized裏面的代碼並且還沒把鎖交出來(釋放)時,其他線程無法獲取
到鎖,故無法執行其中的代碼,在沒有使用synchronized的函數中,因為沒有鎖機制,有可能for循環還沒結束的時候就有新的進程進來,即沒有加到100就進來
故count最後不會到1000,而且也不是一個固定的數字
synchronized的實現原理
synchronized有三種用法:
第一種是對於同步方法,即修飾某個函數,這個時候鎖對象是當前類的實例化對象
第二種是對於同步靜態方法,這個時候鎖是當前類的Class對象
第三種就是我上面的用法,對於同步代碼塊,鎖是括號裏的對象
前面說一個進程會獲得鎖,那麽這個鎖是存在在什麽地方呢
首先需要了解java對象頭,對象頭分為兩種
普通對象的對象頭占兩個字寬
數組對象的對象頭占三個字寬,相比於普通對象的對象頭多了一個字寬來存儲數組長度
java對象頭中的Markword字段中存儲對象的HashCode,分代年齡和鎖標記位,在運行期間,MarkWord存儲的數據會隨著鎖標誌位
的變化為變化,MarkWord的結構是這個樣子的
這個鎖的標誌位就是鎖的類型了,也就是之前所說的"鎖"
Volatile
java對volatile的定義是:Java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致地更新,線程應該確保通過排他鎖單獨獲得這個變量,
如果一個字段被申明為volatile,線程內存模型確保所有線程能夠看到的這個變量的值是一致的
那麽volatile是怎麽保證可見性的呢
我們知道處理器為了保證處理的速度,是不和系統內存直接通信的,因為內存的速度遠遠慢yu每個處理器先是將系統內存中的數據讀入到CPU內部緩存中
再進行操作,但是操作完成之後是不知道什麽時候緩存中的數據會寫到內存中的,那麽其他處理器讀取這個變量的時候就有可能讀取到舊的數據,volatile是
這樣子做的,一旦被volatile修飾的變量發生讀寫操作,就會發生兩個動作:
1,將當前處理器緩存行的數據寫到系統內存中(這是Lock前綴指令會引起的)
2,然後這個操作使得其他CPU緩存了該內存地址的數據無效
然後我們知道的是,每個處理器中的緩存是一致的,那麽這個時候就需要緩存一致性協議,每個處理器嗅探總線上傳播的數據來檢測自己緩存中的值是否過期,
當發現自己的緩存行對應的內存地址被修改,就會將自己緩存行設置為無效,當需要修改的時候,重新從系統內存讀取到緩存行,這樣就實現了可見性
volatile和synchronized關鍵字