1. 程式人生 > >volatile和synchronized關鍵字

volatile和synchronized關鍵字

++ i++ 區別 .get style src 的區別 內部 需要

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;
        lock 
= 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++; } } } }
View Code

要進行的操作是對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();
        
for (Thread o:threads) o.join(); System.out.println(syn.getCount());
View Code

再看運行結果

技術分享圖片

可是結果卻不是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關鍵字