<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Dict.CN 在線詞典, 英語學(xué)習(xí), 在線翻譯

    都市淘沙者

    荔枝FM Everyone can be host

    統(tǒng)計

    留言簿(23)

    積分與排名

    優(yōu)秀學(xué)習(xí)網(wǎng)站

    友情連接

    閱讀排行榜

    評論排行榜

    ConcurrentHashMap原理分析


    ConcurrentHashMap原理分析


    一.Java并發(fā)基礎(chǔ)

    當(dāng)一個對象或變量可以被多個線程共享的時候,就有可能使得程序的邏輯出現(xiàn)問題。
    在一個對象中有一個變量i=0,有兩個線程A,B都想對i加1,這個時候便有問題顯現(xiàn)出來,關(guān)鍵就是對i加1的這個過程不是原子操作。要想對i進(jìn)行遞增, 第一步就是獲取i的值,當(dāng)A獲取i的值為0,在A將新的值寫入A之前,B也獲取了A的值0,然后A寫入,i變成1,然后B也寫入i,i這個時候依然是1.
    當(dāng)然java的內(nèi)存模型沒有上面這么簡單,在Java Memory Model中,Memory分為兩類,

    main memory和working memory,main memory為所有線程共享,working memory中存放的是線程所需要的變量的拷貝(線程要對main memory中的內(nèi)容進(jìn)行操作的話,首先需要拷貝到自己的working memory,一般為了速度,working memory一般是在cpu的cache中的)。volatile的變量在被操作的時候不會產(chǎn)生working memory的拷貝,而是直接操作main memory,當(dāng)然volatile雖然解決了變量的可見性問題,但沒有解決變量操作的原子性的問題,這個還需要synchronized或者CAS相關(guān) 操作配合進(jìn)行。

    多線程中幾個重要的概念:

    可見性

    也就說假設(shè)一個對象中有一個變量i,那么i是保存在main memory中的,當(dāng)某一個線程要操作i的時候,首先需要從main memory中將i 加載到這個線程的working memory中,這個時候working memory中就有了一個i的拷貝,這個時候此線程對i的修改都在其working memory中,直到其將i從working memory寫回到main memory中,新的i的值才能被其他線程所讀取。從某個意義上說,可見性保證了各個線程的working memory的數(shù)據(jù)的一致性。
    可見性遵循下面一些規(guī)則:

    • 當(dāng)一個線程運行結(jié)束的時候,所有寫的變量都會被flush回main memory中。
    • 當(dāng)一個線程第一次讀取某個變量的時候,會從main memory中讀取最新的。
    • volatile的變量會被立刻寫到main memory中的,在jsr133中,對volatile的語義進(jìn)行增強(qiáng),后面會提到
    • 當(dāng)一個線程釋放鎖后,所有的變量的變化都會flush到main memory中,然后一個使用了這個相同的同步鎖的進(jìn)程,將會重新加載所有的使用到的變量,這樣就保證了可見性。

    原子性

    還拿上面的例子來說,原子性就是當(dāng)某一個線程修改i的值的時候,從取出i到將新的i的值寫給i之間不能有其他線程對i進(jìn)行任何操作。也就是說保證某個線程對i的操作是原子性的,這樣就可以避免數(shù)據(jù)臟讀。
    通過鎖機(jī)制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保證操作的原子性。

    有序性

    假設(shè)在main memory中存在兩個變量i和j,初始值都為0,在某個線程A的代碼中依次對i和j進(jìn)行自增操作(i,j的操作不相互依賴),

    1
    2
    i++;
    j++;

    由于,所以i,j修改操作的順序可能會被重新排序。那么修改后的ij寫到main memory中的時候,順序可能就不是按照i,j的順序了,這就是所謂的reordering, 在單線程的情況下,當(dāng)線程A運行結(jié)束的后i,j的值都加1了,在線程自己看來就好像是線程按照代碼的順序進(jìn)行了運行(這些操作都是基于as-if- serial語義的),即使在實際運行過程中,i,j的自增可能被重新排序了,當(dāng)然計算機(jī)也不能幫你亂排序,存在上下邏輯關(guān)聯(lián)的運行順序肯定還是不會變 的。但是在多線程環(huán)境下,問題就不一樣了,比如另一個線程B的代碼如下

    1
    2
    3
    if(j==1) {
        System.out.println(i);
    }

    按照我們的思維方式,當(dāng)j為1的時候那么i肯定也是1,因為代碼中i在j之前就自增了,但實際的情況有可能當(dāng)j為1的時候i還是為0。這就是 reordering產(chǎn)生的不好的后果,所以我們在某些時候為了避免這樣的問題需要一些必要的策略,以保證多個線程一起工作的時候也存在一定的次序。 JMM提供了happens-before 的排序策略。這樣我們可以得到多線程環(huán)境下的as-if-serial語義。
    這里不對happens-before進(jìn)行詳細(xì)解釋了,詳細(xì)的請看這里http://www.ibm.com/developerworks/cn/java/j-jtp03304/,這里主要講一下volatile在新的java內(nèi)存模型下的變化,在jsr133之前,下面的代碼可能會出現(xiàn)問題

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Map configOptions;
    char[] configText;
    volatile boolean initialized = false;
    // In Thread A
    configOptions = new HashMap();
    configText = readConfigFile(fileName);
    processConfigOptions(configText, configOptions);
    initialized = true;
    // In Thread B
    while (!initialized)
      sleep();
    // use configOptions

    jsr133之前,雖然對 volatile 變量的讀和寫不能與對其他 volatile 變量的讀和寫一起重新排序,但是它們?nèi)匀豢梢耘c對 nonvolatile 變量的讀寫一起重新排序,所以上面的Thread A的操作,就可能initialized變成true的時候,而configOptions還沒有被初始化,所以initialized先于 configOptions被線程B看到,就產(chǎn)生問題了。

    JSR 133 Expert Group 決定讓 volatile 讀寫不能與其他內(nèi)存操作一起重新排序,新的內(nèi)存模型下,如果當(dāng)線程 A 寫入 volatile 變量 V 而線程 B 讀取 V 時,那么在寫入 V 時,A 可見的所有變量值現(xiàn)在都可以保證對 B 是可見的。

    結(jié)果就是作用更大的 volatile 語義,代價是訪問 volatile 字段時會對性能產(chǎn)生更大的影響。這一點在ConcurrentHashMap中的統(tǒng)計某個segment元素個數(shù)的count變量中使用到了。

    二.線程安全的HashMap

    什么時候我們需要使用線程安全的hashmap呢,比如一個hashmap在運行的時候只有讀操作,那么很明顯不會有問題,但是當(dāng)涉及到同時有改變 也有讀的時候,就要考慮線程安全問題了,在不考慮性能問題的時候,我們的解決方案有Hashtable或者 Collections.synchronizedMap(hashMap),這兩種方式基本都是對整個hash表結(jié)構(gòu)做鎖定操作的,這樣在鎖表的期間, 別的線程就需要等待了,無疑性能不高。

    三.ConcurrentHashMap實現(xiàn)原理

    數(shù)據(jù)結(jié)構(gòu)

    ConcurrentHashMap的目標(biāo)是實現(xiàn)支持高并發(fā)、高吞吐量的線程安全的HashMap。當(dāng)然不能直接對整個hashtable加鎖,所以在ConcurrentHashMap中,數(shù)據(jù)的組織結(jié)構(gòu)和HashMap有所區(qū)別。

    一個ConcurrentHashMap由多個segment組成,每一個segment都包含了一個HashEntry數(shù)組的hashtable,
    每一個segment包含了對自己的hashtable的操作,比如get,put,replace等操作,這些操作發(fā)生的時候,對自己的 hashtable進(jìn)行鎖定。由于每一個segment寫操作只鎖定自己的hashtable,所以可能存在多個線程同時寫的情況,性能無疑好于只有一個 hashtable鎖定的情況。

    源碼分析

    在ConcurrentHashMap的remove,put操作還是比較簡單的,都是將remove或者put操作交給key所對應(yīng)的segment去做的,所以當(dāng)幾個操作不在同一個segment的時候就可以并發(fā)的進(jìn)行。

    1
    2
    3
    4
        public V remove(Object key) {
        int hash = hash(key.hashCode());
            return segmentFor(hash).remove(key, hash, null);
        }

    而segment中的remove操作除了加鎖之外和HashMap中的remove操作基本無異。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
            /**
             * Remove; match on key only if value null, else match both.
             */

            V remove(Object key, int hash, Object value) {
                lock();
                try {
                    int c = count - 1;
                    HashEntry<K,V>[] tab = table;
                    int index = hash & (tab.length - 1);
                    HashEntry<K,V> first = tab[index];
                    HashEntry<K,V> e = first;
                    while (e != null && (e.hash != hash || !key.equals(e.key)))
                        e = e.next;

                    V oldValue = null;
                    if (e != null) {
                        V v = e.value;
                        if (value == null || value.equals(v)) {
                            oldValue = v;
                            // All entries following removed node can stay
                            // in list, but all preceding ones need to be
                            // cloned.
                            ++modCount;
                            HashEntry<K,V> newFirst = e.next;
                            for (HashEntry<K,V> p = first; p != e; p = p.next)
                                newFirst = new HashEntry<K,V>(p.key, p.hash,
                                                              newFirst, p.value);
                            tab[index] = newFirst;
                            count = c; // write-volatile
                        }
                    }
                    return oldValue;
                } finally {
                    unlock();
                }
            }

    上面的代碼中關(guān)于volatile類型的變量count值得一提,這里充分利用了Java 5中對volatile語義的增強(qiáng),count = c的操作必須在modCount,table等操作的后面,這樣才能保證這些變量操作的可見性。
    Segment類繼承于ReentrantLock,主要是為了使用ReentrantLock的鎖,ReentrantLock的實現(xiàn)比
    synchronized在多個線程爭用下的總體開銷小。
    put操作和remove操作類似。

    接下來我們來看下get操作。

    1
    2
    3
    4
        public V get(Object key) {
            int hash = hash(key.hashCode());
            return segmentFor(hash).get(key, hash);
        }

    也是使用了對應(yīng)的segment的get

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
           V get(Object key, int hash) {
                if (count != 0) { // read-volatile
                    HashEntry<K,V> e = getFirst(hash);
                    while (e != null) {
                        if (e.hash == hash && key.equals(e.key)) {
                            V v = e.value;
                            if (v != null)
                                return v;
                            return readValueUnderLock(e); // recheck
                        }
                        e = e.next;
                    }
                }
                return null;
            }

    上面的代碼中,一開始就對volatile變量count進(jìn)行了讀取比較,這個還是java5對volatile語義增強(qiáng)的作用,這樣就可以獲取變 量的可見性。所以count != 0之后,我們可以認(rèn)為對應(yīng)的hashtable是最新的,當(dāng)然由于讀取的時候沒有加鎖,在get的過程中,可能會有更新。當(dāng)發(fā)現(xiàn)根據(jù)key去找元素的時 候,但發(fā)現(xiàn)找得的key對應(yīng)的value為null,這個時候可能會有其他線程正在對這個元素進(jìn)行寫操作,所以需要在使用鎖的情況下在讀取一下 value,以確保最終的值。

    其他相關(guān)涉及讀取的操作也都類似。

    posted on 2010-12-07 10:50 都市淘沙者 閱讀(872) 評論(0)  編輯  收藏 所屬分類: Java Basic/Lucene/開源資料

    主站蜘蛛池模板: 精品人妻系列无码人妻免费视频 | 亚洲免费在线观看视频| 免费无码H肉动漫在线观看麻豆| 日韩成人免费在线| 亚洲а∨天堂久久精品9966| 69成人免费视频| 亚洲国产成人精品激情| h视频在线免费看| 亚洲成人福利在线| 最近的免费中文字幕视频| 亚洲 欧洲 自拍 另类 校园| 国产精品怡红院永久免费| 亚洲fuli在线观看| 韩国18福利视频免费观看| 一区二区无码免费视频网站| 亚洲欧美成人一区二区三区| 午夜老司机免费视频| 边摸边脱吃奶边高潮视频免费| 亚洲A∨午夜成人片精品网站 | 国产成人无码区免费A∨视频网站 国产成人涩涩涩视频在线观看免费 | 亚洲av无码专区国产乱码在线观看| 男人进去女人爽免费视频国产| 亚洲国产人成网站在线电影动漫| a毛片全部播放免费视频完整18| 亚洲影院在线观看| 在线视频免费国产成人| 最新国产乱人伦偷精品免费网站| 亚洲天堂中文字幕在线观看| 国产福利免费在线观看| 免费在线黄色电影| 亚洲av日韩aⅴ无码色老头| 亚洲色无码一区二区三区| 在线观看免费人成视频色| 和老外3p爽粗大免费视频| 内射少妇36P亚洲区| 免费一级做a爰片性色毛片| 久久99热精品免费观看牛牛| 亚洲欧洲日韩极速播放| 亚洲av永久无码精品漫画| 巨胸喷奶水视频www网免费| 日本免费一区二区久久人人澡 |