<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何解決Java集合線程不安全的問題

          共 5372字,需瀏覽 11分鐘

           ·

          2021-07-14 02:25

          先從一個問題開始:Java中的ArrayList是線程安全的嗎?

          大家都知道是線程不安全的,那么問題來了,你如何去證明它是線程不安全的呢?那好,寫個例子吧:


          public static void main(String[] args) {
                  List list = new ArrayList();
                  list.add(1);
                  list.add(2);
                  list.add(3);
                  list.add(4);
                  list.add(5);
                  System.out.println(list);
              }
          OK,以上是熱個身,既然要證明線程不安全,那就得需要多個線程去操作啊,那繼續(xù):


          public static void main(String[] args) {
                  List<String> list = new ArrayList<>();
                  for (int i = 0; i < 3; i++) {
                      new Thread(()->{
                          list.add(UUID.randomUUID().toString().substring(0,8));
                          System.out.println(list);
                      },"線程"+i).start();
                  }
              }
          OK,代碼改寫完成,那這樣會出現(xiàn)什么樣的問題呢?運行看看唄:
          沒啥問題啊?再執(zhí)行一遍看看:
          是不是結(jié)果不一樣了?再執(zhí)行看看:
          咋樣,結(jié)果是不是很是豐富多彩啊,為什么?給你看個圖:
          另外有個很重要的知識點,請問,多線程start開啟之后,他們是順序執(zhí)行還是亂序執(zhí)行?

          答案是亂序執(zhí)行,而且執(zhí)行速度很快,那這就產(chǎn)生問題了,比如1線程還沒有寫,2線程就讀了,或者1和3線程都寫了,2線程才讀……所以,結(jié)果是豐富多彩的!

          那么問題來了:


          public static void main(String[] args) {
                  List<String> list = new ArrayList<>();
                  for (int i = 0; i < 30; i++) {
                      new Thread(()->{
                          list.add(UUID.randomUUID().toString().substring(0,8));
                          System.out.println(list);
                      },"線程"+i).start();
                  }
              }
          看到區(qū)別沒?這里開啟30個線程,請問,程序會報錯嗎?
          程序竟然報錯了,什么錯誤呢?
          為什么?
          ??這是因為ArrayList是線程不安全的,當(dāng)比較多的線程去同時對其進行快速讀寫的時候,它就會發(fā)生崩潰導(dǎo)致并發(fā)修改異常
          那咋辦?

          解決方案一:替換vector

          這個時候你能想到怎么做嗎?加鎖?可以,但是不用你自己加鎖,因為有vector,還記得這貨嗎?


          public static void main(String[] args) {
                  List<String> list = new Vector<>();
                  for (int i = 0; i < 30; i++) {
                      new Thread(()->{
                          list.add(UUID.randomUUID().toString().substring(0,8));
                          System.out.println(list);
                      },"線程"+i).start();
                  }
              }
          看下它的add方法:
          看到?jīng)],它的add上增加了synchronized,所以Vector是線程安全的,但是不要用它,為啥?

          ??記住一句話就行“性能令人不滿意”
          雖然不推薦用這種,但是使用Vector的確可以解決并發(fā)修改的異常,程序運行看下:
          異常消失了!

          解決方案二:Collections

          那還有沒有其他的方法呢?還有如下這種:


          public static void main(String[] args) {
                  List<String> list = Collections.synchronizedList(new ArrayList<>());
                  for (int i = 0; i < 30; i++) {
                      new Thread(()->{
                          list.add(UUID.randomUUID().toString().substring(0,8));
                          System.out.println(list);
                      },"線程"+i).start();
                  }
              }
          也就是使用Java集合的工具類:


          Collections.synchronizedList(new ArrayList<>());
          將線程不安全的list變成線程安全的list,但是這樣的方式依然不推薦,原因還是一樣的,就是性能受到影響!

          所以,盡管此種方式可以解決并發(fā)修改異常,但是依然不推薦!

          我們上面說的使用Vector或者集合工具類的形式,這些都是同步容器,就是通過加鎖的形式,比如Vector的add方法:
          這里是加上了synchronized,如此一來,同一個時間只能有一個線程來訪問,那這樣的話,的確保證了數(shù)據(jù)的讀取一致性,但是效率也就下降了!

          那對于使用集合工具類的形式來說,其實也可以用,為什么不推薦使用呢?因為作為一個看起來很牛逼的程序員,我們有更好的選擇,那就使用到并發(fā)容器!

          解決方案三:寫時復(fù)制

          這是個啥,簡單,看代碼:


          public static void main(String[] args) {
                  List<String> list = new CopyOnWriteArrayList<>();
                  for (int i = 0; i < 30; i++) {
                      new Thread(()->{
                          list.add(UUID.randomUUID().toString().substring(0,8));
                          System.out.println(list);
                      },"線程"+i).start();
                  }
              }
          就是這個CopyOnWriteArrayList,記住了,以后就用這個!

          首先運行一下,肯定是沒問題的:

          那為什么這個就可以用呢?
          看到?jīng)],首先人家就是Java并發(fā)包里面的,所以人家是專門針對并發(fā)的,那么在效率和性能上絕對比其他的強!

          那這個CopyOnWriteArrayList為什么就那么強呢?原理是啥?我們從字面意思去看啥是CopyOnWrite,是不是“復(fù)制在寫”???

          好啦,人家其實叫做“寫時復(fù)制”,是讀寫分離思想的一種,高就高在這,我們來看看具體是咋回事!

          原理初探

          啥叫做寫時復(fù)制,啥又是CopyOnWrite,說實話,看起來很高大上,其實思想很簡單,首先明確這里要達成的一個效果:
          一個線程去執(zhí)行寫操作,多個線程執(zhí)行讀操作
          如此一來讀寫就是被分離開來的,保證了數(shù)據(jù)一致性的同時也保證的效率,那是怎么做的,重點就在這個“copy”上,啥?復(fù)制啊,也就是說,在對一個資源進行讀寫的時候,假如一個線程在進行寫操作,那么這個時候它就獲得相應(yīng)的鎖,此時是不允許其他線程去進行寫操作的,但是其他線程可以進行讀操作,也就是你該寫寫,不耽誤別人的讀操作,而且重點就是,這個線程在寫的這個數(shù)據(jù)不是原數(shù)據(jù)而是拷貝的原數(shù)據(jù),也就是把原數(shù)據(jù)拷貝一份拿來進行寫操作,而原數(shù)據(jù)還在供其他線程讀!

          此時,讀寫就分離開來了,一旦這個線程的寫操作完成,那此時這個數(shù)據(jù)就是最新的數(shù)據(jù),這時就會把原來的那個原數(shù)據(jù)給干掉,只保留寫之后的這個數(shù)據(jù)!

          這就是寫時復(fù)制,就是CopyOnWriteArrayList神秘面紗之后的真相!

          源代碼

          明白了簡單的原理之后,我們看看源代碼!首先看下其底層數(shù)據(jù)類型:
          看到?jīng)],依舊是一個Object數(shù)組,但是容量為0!接下里去看add的方法:
          重點就在這里了,看到?jīng)],這里:


           Object[] newElements = Arrays.copyOf(elements, len + 1); 
          當(dāng)你增加一個元素的時候,底層數(shù)據(jù)就擴容1來容納你增加的這個元素,然后會得到新的數(shù)組并覆蓋掉原來的數(shù)組!

          而在我進行寫操作的時候,這里是加鎖的:
          保證了寫操作只能有一個線程來完成!

          小總結(jié)

          OK,以上就是寫時復(fù)制CopyOnWriteArrayList的一個介紹,下面進行簡單的小總結(jié),所謂的CopyOnWriteArrayList我們一般叫它為寫時復(fù)制的容器,既然是容器那就是裝載數(shù)據(jù)的,通過后面的ArrayList我們也不應(yīng)該覺得很陌生,這家伙就是對我們熟知的ArrayList進行增強!

          當(dāng)你往CopyOnWriteArrayList中去添加容器的時候,不是立馬就往其底層數(shù)組中去添加,而是先把底層的數(shù)組復(fù)制一份,往復(fù)制的這份里面去添加,添加完成之后就把原有的數(shù)組給覆蓋掉,這樣一來,就可以實現(xiàn)單個寫,多個讀,在進行數(shù)據(jù)添加的時候因為是對原數(shù)組的拷貝的數(shù)組進行寫操作,這個是加鎖的,但是對原數(shù)組的讀是不加鎖的,可以實現(xiàn)并發(fā)的讀,這樣,性能效率就都有了!

          是不是很巧妙,這就是一種讀寫分離思想!
          瀏覽 68
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲涩情91日韩一区二区 | 国产精品激情在线 | 国产伊人大香蕉 | 免费18禁网站 | 人妻97 |