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

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

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

    posts - 8, comments - 13, trackbacks - 0, articles - 43
      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

    在平時開發(fā)中,我們經(jīng)常采用HashMap來作為本地緩存的一種實現(xiàn)方式,將一些如系統(tǒng)變量等數(shù)據(jù)量比較少的參數(shù)保存在HashMap中,并將其作為單例類的一個屬性。在系統(tǒng)運行中,使用到這些緩存數(shù)據(jù),都可以直接從該單例中獲取該屬性集合。但是,最近發(fā)現(xiàn),HashMap并不是線程安全的,如果你的單例類沒有做代碼同步或對象鎖的控制,就可能出現(xiàn)異常。

    首先看下在多線程的訪問下,非現(xiàn)場安全的HashMap的表現(xiàn)如何,在網(wǎng)上看了一些資料,自己也做了一下測試:

     1public class MainClass {
     2    
     3    public static final HashMap<String, String> firstHashMap=new HashMap<String, String>();
     4    
     5    public static void main(String[] args) throws InterruptedException {
     6        
     7        //線程一
     8        Thread t1=new Thread(){
     9            public void run() {
    10                for(int i=0;i<25;i++){
    11                    firstHashMap.put(String.valueOf(i), String.valueOf(i));
    12                }

    13            }

    14        }
    ;
    15        
    16        //線程二
    17        Thread t2=new Thread(){
    18            public void run() {
    19                for(int j=25;j<50;j++){
    20                    firstHashMap.put(String.valueOf(j), String.valueOf(j));
    21                }

    22            }

    23        }
    ;
    24        
    25        t1.start();
    26        t2.start();
    27        
    28        //主線程休眠1秒鐘,以便t1和t2兩個線程將firstHashMap填裝完畢。
    29        Thread.currentThread().sleep(1000);
    30        
    31        for(int l=0;l<50;l++){
    32            //如果key和value不同,說明在兩個線程put的過程中出現(xiàn)異常。
    33            if(!String.valueOf(l).equals(firstHashMap.get(String.valueOf(l)))){
    34                System.err.println(String.valueOf(l)+":"+firstHashMap.get(String.valueOf(l)));
    35            }

    36        }

    37        
    38    }

    39
    40}

    上面的代碼在多次執(zhí)行后,發(fā)現(xiàn)表現(xiàn)很不穩(wěn)定,有時沒有異常文案打出,有時則有個異常出現(xiàn):


    為什么會出現(xiàn)這種情況,主要看下HashMap的實現(xiàn):
     1public V put(K key, V value) {
     2    if (key == null)
     3        return putForNullKey(value);
     4        int hash = hash(key.hashCode());
     5        int i = indexFor(hash, table.length);
     6        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
     7            Object k;
     8            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
     9                V oldValue = e.value;
    10                e.value = value;
    11                e.recordAccess(this);
    12                return oldValue;
    13            }

    14        }

    15
    16        modCount++;
    17        addEntry(hash, key, value, i);
    18        return null;
    19    }

    我覺得問題主要出現(xiàn)在方法addEntry,繼續(xù)看:
    1void addEntry(int hash, K key, V value, int bucketIndex) {
    2    Entry<K,V> e = table[bucketIndex];
    3        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    4        if (size++ >= threshold)
    5            resize(2 * table.length);
    6    }

    從代碼中,可以看到,如果發(fā)現(xiàn)哈希表的大小超過閥值threshold,就會調用resize方法,擴大容量為原來的兩倍,而擴大容量的做法是新建一個Entry[]:
     1void resize(int newCapacity) {
     2        Entry[] oldTable = table;
     3        int oldCapacity = oldTable.length;
     4        if (oldCapacity == MAXIMUM_CAPACITY) {
     5            threshold = Integer.MAX_VALUE;
     6            return;
     7        }

     8
     9        Entry[] newTable = new Entry[newCapacity];
    10        transfer(newTable);
    11        table = newTable;
    12        threshold = (int)(newCapacity * loadFactor);
    13    }

    一般我們聲明HashMap時,使用的都是默認的構造方法:HashMap<K,V>,看了代碼你會發(fā)現(xiàn),它還有其它的構造方法:HashMap(int initialCapacity, float loadFactor),其中參數(shù)initialCapacity為初始容量,loadFactor為加載因子,而之前我們看到的threshold = (int)(capacity * loadFactor); 如果在默認情況下,一個HashMap的容量為16,加載因子為0.75,那么閥值就是12,所以在往HashMap中put的值到達12時,它將自動擴容兩倍,如果兩個線程同時遇到HashMap的大小達到12的倍數(shù)時,就很有可能會出現(xiàn)在將oldTable轉移到newTable的過程中遇到問題,從而導致最終的HashMap的值存儲異常。

    JDK1.0引入了第一個關聯(lián)的集合類HashTable,它是線程安全的。HashTable的所有方法都是同步的。
    JDK2.0引入了HashMap,它提供了一個不同步的基類和一個同步的包裝器synchronizedMap。synchronizedMap被稱為有條件的線程安全類。
    JDK5.0util.concurrent包中引入對Map線程安全的實現(xiàn)ConcurrentHashMap,比起synchronizedMap,它提供了更高的靈活性。同時進行的讀和寫操作都可以并發(fā)地執(zhí)行。

    所以在開始的測試中,如果我們采用ConcurrentHashMap,它的表現(xiàn)就很穩(wěn)定,所以以后如果使用Map實現(xiàn)本地緩存,為了提高并發(fā)時的穩(wěn)定性,還是建議使用ConcurrentHashMap。


    ====================================================================

    另外,還有一個我們經(jīng)常使用的ArrayList也是非線程安全的,網(wǎng)上看到的有一個解釋是這樣:
    一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
    在單線程運行的情況下,如果 Size = 0,添加一個元素后,此元素在位置 0,而且 Size=1;
    而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B也將元素放在位置0,(因為size還未增長),完了之后,兩個線程都是size++,結果size變成2,而只有items[0]有元素。
    util.concurrent包也提供了一個線程安全的ArrayList替代者CopyOnWriteArrayList。
    
    


    評論

    # re: 并發(fā)實踐之一:HashMap的非線程安全性和ConcurrentHasMap  回復  更多評論   

    2010-09-06 12:27 by xylz
    "如果兩個線程同時遇到HashMap的大小達到12的倍數(shù)時,就很有可能會出現(xiàn)在將oldTable轉移到newTable的過程中遇到問題,從而導致最終的HashMap的值存儲異常"

    如果只有一個線程到達閾值,另一個線程讀寫同樣會有問題,因為rehash的時候同一個元素的索引index會變化(不是固定的)。

    # re: 并發(fā)實踐之一:HashMap的非線程安全性和ConcurrentHasMap  回復  更多評論   

    2010-09-06 17:19 by tanlun
    @xylz
    是的,多次出現(xiàn)25=null,不是巧合。

    # re: 并發(fā)實踐之一:HashMap的非線程安全性和ConcurrentHasMap[未登錄]  回復  更多評論   

    2014-01-04 10:08 by xx
    這個例子是錯誤的呀!

    # re: 并發(fā)實踐之一:HashMap的非線程安全性和ConcurrentHasMap[未登錄]  回復  更多評論   

    2014-01-04 10:10 by xx
    import java.util.HashMap;
    import java.util.Map;

    public class TestHashMap {

    public static final HashMap<String, String> firstHashMap=new HashMap<String, String>();
    public static void main(String[] args) throws InterruptedException {

    //線程一
    Thread t1=new Thread(){
    public void run() {
    for(int i=0;i<25;i++){
    firstHashMap.put(String.valueOf(i), String.valueOf(i));
    }
    }
    };

    //線程二
    Thread t2=new Thread(){
    public void run() {
    for(int j=25;j<50;j++){
    firstHashMap.put(String.valueOf(j), String.valueOf(j));
    }
    }
    };

    t1.start();
    t2.start();

    //主線程休眠1秒鐘,以便t1和t2兩個線程將firstHashMap填裝完畢。
    Thread.currentThread().sleep(10000);

    for(int l=0;l<50;l++){
    //如果key和value不同,說明在兩個線程put的過程中出現(xiàn)異常。
    //System.out.println(firstHashMap.get(String.valueOf(l))+" "+l);
    if(!String.valueOf(l).equals(firstHashMap.get(String.valueOf(l)))){
    System.err.println(String.valueOf(l)+":"+firstHashMap.get(String.valueOf(l)));
    //System.out.println("===="+firstHashMap.get(String.valueOf(l))+" "+l);
    }
    }
    System.out.println(firstHashMap.size());
    System.out.println("ddd==========");
    for(Map.Entry<String,String> item:firstHashMap.entrySet()){
    System.out.println(item.getKey()+" "+item.getValue());
    }
    }

    }

    遍歷map結果完全正確,多次執(zhí)行也還是正確的。源程序輸出異常是因為兩個線程還沒有執(zhí)行完吧。

    # re: 并發(fā)實踐之一:HashMap的非線程安全性和ConcurrentHasMap  回復  更多評論   

    2015-04-01 11:19 by xxxx
    不是很懂!
    主站蜘蛛池模板: 麻豆一区二区三区蜜桃免费| 成人免费网站在线观看| 亚洲精品无码久久久久久| 亚洲日韩中文无码久久| 国产精品深夜福利免费观看 | 亚洲精品成人久久久| 欧洲一级毛片免费| 国内精品免费视频精选在线观看 | 午夜老司机永久免费看片| 免费福利在线观看| 亚洲综合伊人制服丝袜美腿| 亚洲国产美女精品久久久久∴| 国产人成免费视频| 久久久久国色AV免费看图片| 免费无码中文字幕A级毛片| caoporn国产精品免费| 亚洲成av人在线观看网站| 亚洲国产模特在线播放| 亚洲精品高清视频| 亚洲日韩中文字幕在线播放| 亚洲欧洲精品成人久久曰影片| 国产小视频在线免费| 成人免费看黄20分钟| 国产美女在线精品免费观看| 91精品免费久久久久久久久| 青青青国产手机频在线免费观看 | 女人18毛片免费观看| 国产卡二卡三卡四卡免费网址| 最近2019中文字幕免费直播| 久久一区二区三区免费播放 | 亚洲日韩国产一区二区三区| 四虎永久在线精品免费影视| 国产公开免费人成视频| 日本特黄特色aa大片免费| 免费网站看v片在线香蕉| 四虎在线视频免费观看| 四虎永久在线精品免费网址| 操美女视频免费网站| 麻豆成人精品国产免费| 国产成人免费ā片在线观看| 国产免费看插插插视频|