JDK1.8逐字逐句帶你理解ConcurrentHashMap

引言:

在前幾篇博文中我詳細介紹了HashMap的底層實現原理,後來我接連寫了三天JVM和GC的一些知識,那些知識偏向於理論。今天換點口味,和大家一起研究學習一下ConcurrentHashMap的底層實現,因爲jdk1.8在HashMap和concurrentHashMap和以往都發生了變化。知識一直都是連續的,如果我覺得光說ConcurrentHashMap的源碼,很多小夥伴會雲裏霧裏,所以爲了讓所有人,甚至是新手都能通透理解,限於篇幅和時間,我打算把ConcurrentHashMap分爲三部分來寫,第一篇作爲基礎,第二篇爲認識,第三篇爲熟知。今天第一篇我主要介紹一下Java內存模型,volatile關鍵字和CAS算法,如果理解這三點,對後面的源碼理解會有極大的幫助。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290

技術點:

1、悲觀鎖與樂觀鎖
悲觀鎖是指如果一個線程佔用了一個鎖,而導致其他所有需要這個鎖的線程進入等待,一直到該鎖被釋放,換句話說就是這個鎖被獨佔,比如說典型的就是synchronized;樂觀鎖是指操作並不加鎖,而是抱着嘗試的態度去執行某項操作,如果操作失敗或者操作衝突,那麼就進入重試,一直到執行成功爲止。

2、原子性,指令有序性和線程可見性
這三個性質在多線程編程中是核心的問題。原子性和事務的原子性一樣,對於一個操作或者多個操作,要麼都執行,要麼都不執行。指令有序性是指,在我們編寫的代碼中,上下兩個互不關聯的語句不會被指令重排序。指令重排序是指處理器爲了性能優化,在無關聯的代碼的執行是可能會和代碼順序不一致。比如說int i = 1;int j = 2;那麼這兩條語句的執行順序可能會先執行int j = 2;線程可見性是指一個線程修改了某個變量,其他線程能馬上知道。

3、無鎖算法(nonblocking algorithms):
使用低層原子化的機器指令, 保證併發情況下數據的完整性。典型的如CAS算法。

4、內存屏障:
在《深入理解JVM》中解釋是:它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;它會強制將對緩存的修改操作立即寫入主存;如果是寫操作,它會導致其他CPU中對應的緩存行無效。在使用volatile修飾的變量會產生內存屏障(後面會詳細解釋)。

Java內存模型

下面是我從百度上引入的一張具有代表性的圖:
這裏寫圖片描述

①解釋:我根據這張圖來解釋java內存模型,從圖中可以看出每個線程都需要從主內存中讀取操作,這個就是java內存模型的規定之一,所有的變量存儲在主內存中,每個線程都需要從主內存中獲得變量的值。

然後從圖中可以看到每個線程獲得數據之後會放入自己的工作內存,這個就是java內存模型的規定之二,保證每個線程操作的都是從主內存拷貝的副本,也就是說線程不能直接寫主內存的變量,需要把主內存的變量值讀取之後放入自己的工作內存中的變量副本中,然後操作這個副本。

最後線程與線程之間無法直接訪問對方工作內存中的變量。最後需要解釋一下這個訪問規則侷限於對象實例字段,靜態字段等,局部變量不包括在內,因爲局部變量不存在競爭問題。

②基本執行步驟:
a、lock(鎖定):在某一個線程在讀取主內存的時候需要把變量鎖定。
b、unlock(解鎖):某一個線程讀取玩變量值之後會釋放鎖定,別的線程就可以進入操作
c、read(讀取):從主內存中讀取變量的值並放入工作內存中
d、load(加載):從read操作得到的值放入工作內存變量副本中
e、use(使用):把工作內存中的一個變量值傳遞給執行引擎
f、assign(賦值):它把一個從執行引擎接收到的值賦值給工作內存的變量
g、store(存儲):把工作內存中的一個變量的值傳送到主內存中
h、write(寫入):把store操作從工作內存中一個變量的值傳送到主內存的變量中。

這裏我再引入一張別的地方被我搜來的圖供大家一起理解:

這裏寫圖片描述

volatile關鍵字

在基本清除了java內存模型之後,我們開始詳細說明一下volatile關鍵字,在concurrentHashMap之中,有很多的成員變量都是用volatile修飾的。被volatile修飾的變量有如下特性:

①使得變量更新變得具有可見性,只要被volatile修飾的變量的賦值一旦變化就會通知到其他線程,如果其他線程的工作內存中存在這個同一個變量拷貝副本,那麼其他線程會放棄這個副本中變量的值,重新去主內存中獲取

②產生了內存屏障,防止指令進行了重排序,關於這點的解釋,請看下面一段代碼:

package com.brickworkers;

public class VolatileTest {

    int a = 0;                 //1
    int b = 1;                 //2
    volatile int c = 2;        //3
    int d = 3;                 //4
    int e = 4;                 //5

}

在如上的代碼中,因爲c變量是用volatile進行修飾,那麼就會對該段代碼產生一個內存屏障,用以保證在執行語句3的時候語句1和語句2是絕對執行完畢的,而且在執行語句3的時候,語句4和語句5肯定沒有執行。同時說明一下,在上述代碼中雖然保證了語句3的執行順序不可變換,但是語句1和語句2,語句4和語句5可能發生指令重排序哦。

總結:volatile修飾的變量具有可見性與有序性。

下面,我們用一段代碼來進一步解釋volatile和原子性的概念:

package com.brickworkers;

public class VolatileTest {

//  int a = 0;                 //1
//  int b = 1;                 //2
    public static volatile int c = 0;        //3
//  int d = 3;                 //4
//  int e = 4;                 //5


    public static void increase(){
        c++;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                public void run() {
                    increase();
                }
            }
            ).start();
        }
        Thread.sleep(5000);
        System.out.println(c);
    }
}

//運行3次結果分別是:997,995,989

這個就是典型的volatile操作與原子性的概念。執行結果是小於等於1000的,爲什麼會這樣呢?不是說volatile修飾的變量是具有原子性的麼?是的,volatile修飾的變量的確具有原子性,也就是c是具有原子性的(直接賦值是原子性的),但是c++不具有原子性,c++其實就是c = c +1,已經存在了多步操作。所以c具有原子性,但是c++這個操作不具有原子性。

根據前面介紹的java內存模型,當有一個線程去讀取主內存的過程中獲取c的值,並拷貝一份放入自己的工作內存中,在對c進行+1操作的時候線程阻塞了(各種阻塞情況),那麼這個時候有別的線程進入讀取c的值,因爲有一個線程阻塞就導致該線程無法體現出可見性,導致別的線程的工作內存不會失效,那麼它還是從主內存中讀取c的值,也會正常的+1操作。如此便導致了結果是小於等於1000的。

注意,這裏筆者也有個沒有深刻理解的問題,首先在java內存模型中規定了:在對主內存的unlock操作之前必須要執行write操作,那意思就是c在寫回之前別的線程是無法讀取c的。然而結果卻並非如此。如果哪位朋友能理解其中的原委,請與我聯繫,大家一起討論研究。

CAS算法

CAS的全稱叫“Compare And Swap”,也就是比較與交換,他的主要操作思想是:
首先它具有三個操作數,a、內存位置V,預期值A和新值B。如果在執行過程中,發現內存中的值V與預期值A相匹配,那麼他會將V更新爲新值A。如果預期值A和內存中的值V不相匹配,那麼處理器就不會執行任何操作。CAS算法就是我再技術點中說的“無鎖定算法”,因爲線程不必再等待鎖定,只要執行CAS操作就可以,會在預期中完成。

在ConcurrentHashMap中,很多的操作都會依靠CAS算法完成,具體的在後面兩篇博文再深入體會。對於ConcurrentHashMap的基礎就先寫到這裏,中間還有一些筆者也沒有參悟,希望大家來一起研究。

如果博文存在什麼問題,或者有什麼想法,可以聯繫我呀,下面是我的微信二維碼:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章