乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      關(guān)于Java集合,還停留在ArrayList、HashSet層面嗎

       貪挽懶月 2022-06-20 發(fā)布于廣東

      我們平時編碼時使用集合類,都是new 一個 ArrayList 或者 HashSet 或者 HashMap就直接開用,好像也沒遇到啥問題。但是多線程情況下就會有問題。下面一一道來。

      List

      1、故障現(xiàn)象:
      先看下面一段代碼:

      1 List<String> list = new ArrayList<>();
      2 for (int x = 0; x < 30; x ++){
      3        new Thread( () -> {
      4            list.add("哈哈");
      5        }).start();
      6 }
      7 System.out.println(list.toString());

      這段代碼很簡單,就是創(chuàng)建30個線程,每個線程往list集合add元素,看似沒啥問題,看代碼的運(yùn)行結(jié)果:

      運(yùn)行結(jié)果


      運(yùn)行拋異常了,這便是并發(fā)修改異常。

      2、導(dǎo)致原因:
      并發(fā)修改異常是因?yàn)榫€程并發(fā)爭搶修改導(dǎo)致。舉個例子:上課的時候老師拿了一份名單要點(diǎn)名,說來了的同學(xué)就上去簽自己的名字。這份名單就是集合,每個同學(xué)就是一個線程。上去簽名就是往集合中添加元素的add操作。當(dāng)張三同學(xué)上去簽名的時候,剛寫完 “張” 字,李四同學(xué)就上來把筆搶了去,結(jié)果就是張三同學(xué)的名只簽了一半。這就是并發(fā)修改異常。

      3、解決方案:

      • 第一種辦法,可以使用線程安全的Vector類,它的方法都加了鎖,可以保證線程安全。不過Vector現(xiàn)在很少人用,因?yàn)椴l(fā)性不好。

      • 第二種辦法,使用Collections工具類。如下:

      1List<String> list = Collections.synchronizedList(new ArrayList<>());

      這個方法顧名思義,就是可以把ArrayList變成安全的。所以它也可以解決并發(fā)修改異常。

      • 第三種辦法,使用JUC包中的CopyOnWriteArrayList類。CopyOnWrite的意思是寫時復(fù)制。看看如何使用它解決并發(fā)修改異常。

      1List<String> list = new CopyOnWriteArrayList<>();

      就是new 一個 CopyOnWriteArrayList就可以了。那么這個類為什么能保證線程安全呢?看一下它的源碼:

       1 public boolean add(E e{
      2        final ReentrantLock lock = this.lock;
      3        lock.lock();
      4        try {
      5            Object[] elements = getArray();
      6            int len = elements.length;
      7            Object[] newElements = Arrays.copyOf(elements, len + 1);
      8            newElements[len] = e;
      9            setArray(newElements);
      10            return true;
      11        } finally {
      12            lock.unlock();
      13        }
      14}

      所謂寫時復(fù)制,就是寫的時候不是直接在原來的數(shù)組中寫,而是先復(fù)制一份,寫完后再引用這個新的。還是簽名的例子:老師說同學(xué)們一個個地上來簽名。張三上去了,把那份名單copy了一份,簽上了自己的名字。在張三簽名的過程中,其他同學(xué)還是可以讀老師的那份名單的。當(dāng)張三簽完了,然后再告訴同學(xué)們,之前那份名單作廢了,現(xiàn)在用這份新的。這就是整個過程,對應(yīng)了上面的代碼。首先用lock鎖住這段代碼,即張三簽名過程中其他同學(xué)不能再來搶筆了;然后獲取到原來的數(shù)組,定義一個新數(shù)組,長度為原來的數(shù)組加1,把原數(shù)組內(nèi)容復(fù)制到新數(shù)組中,這是張三復(fù)制名單的過程;然后將要add的元素添加到新數(shù)組的最后,這就是張三寫自己名字的過程;再后來將引用指向新數(shù)組,這是張三告訴大家用這份新名單的過程;最后釋放鎖,也就是張三把筆放下,下一個同學(xué)可以去簽名了。
      這也就是讀寫分離的思想,寫的時候復(fù)制原來的,寫操作完成前,讀數(shù)據(jù)還是讀原來的,寫完成后,讀新的。

      Set
      • 在說Set不安全之前先簡單地說一下HashSet底層是數(shù)據(jù)結(jié)構(gòu):
        HashSet底層是由HashMap實(shí)現(xiàn)的,HashMap的key就是set集合add的元素,而HashMap的value是一個Object類型的常量。

      1、故障現(xiàn)象:

      1ist<String> set = new HashSet<>();
      2 for (int x = 0; x < 30; x ++){
      3        new Thread( () -> {
      4            set.add("哈哈");
      5        }).start();
      6 }
      7 System.out.println(set.toString());

      把上面的ArrayList換成HashSet,一樣會報并發(fā)修改異常。導(dǎo)致原因也是一樣的,下面直接看看解決原因。

      2、解決方案:

      • 使用Collections工具類的synchronizedSet方法。

      • 使用CopyOnWriteArraySet類。注意這個類,實(shí)際上還是CopyOnWriteArrayList類??此鼧?gòu)造方法的源碼就可以知道了。構(gòu)造方法如下:

      1 public CopyOnWriteArraySet() {
      2        al = new CopyOnWriteArrayList<E>();
      3 }
      Map

      Map集合同樣會出現(xiàn)上述問題。很容易讓人想到解決方案也是和上面一樣,其實(shí)有點(diǎn)區(qū)別。首先,的確可以使用Collections工具類的synchronizedMap方法,其次,也可以使用HashTable。HashTable所有的方法都加了鎖,所以可以保證安全。但是也正因它所有方法都加了鎖,并發(fā)性不好,所以不推薦使用。第三種辦法,可能會想到寫時復(fù)制,其實(shí)java沒有為map提供寫時復(fù)制的類。我們可以使用ConcurrentHashMap,這個也是線程安全的,而且性能還不錯。它是使用了CAS來保證安全性。我另一篇文章《Java源碼解讀---HashMap&ConcurrentHashMap》中有介紹,大家可以參考一下。

      • Collections.synchronizedXxx原理:
        上面說到解決List、Set、Map的安全問題都可以使用Collections工具類,那么它原理是什么呢?來看一下源碼(拿synchronizedList來說明):

      1public static <T> List<T> synchronizedList(List<T> list) {
      2        return (list instanceof RandomAccess ?
      3                new SynchronizedRandomAccessList<>(list) :
      4                new SynchronizedList<>(list));
      5}

      首先它判斷你new的集合有沒有實(shí)現(xiàn)RandomAccess接口 (這個接口是一個標(biāo)記接口,ArrayList就實(shí)現(xiàn)了這個接口。作用就是,如果實(shí)現(xiàn)了這個接口,那么就說明支持快速隨機(jī)訪問,如果支持快速隨機(jī)方法,那么取元素的時候就用for循環(huán),否則就用迭代器。這是因?yàn)椋绻恢С挚焖匐S機(jī)訪問,用迭代器獲取元素效率會更高。ArrayList由數(shù)組實(shí)現(xiàn),可以通過索引獲取元素,顯然是支持快速隨機(jī)訪問) 。然后 new SynchronizedRandomAccessList<>(list);其實(shí)就是對傳進(jìn)去的list的方法加上了同步代碼塊,所以可以保證線程安全。它和Vector、HashTable的區(qū)別也就在于,它使用的是同步代碼塊,而后兩者使用的是同步方法。

        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多