<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Loading

      【線程安全】 三類線程安全問題

      什么是線程安全

      《Java Concurrency In Practice》的作者 Brian Goetz 對線程安全是這樣理解的,當多個線程訪問一個對象時,如果不用考慮這些線程在運行時環境下的調度和交替執行問題,也不需要進行額外的同步,而調用這個對象的行為都可以獲得正確的結果,那這個對象便是線程安全的。

      通俗來講,就是在多線程并發的情況下,每個線程的執行結果始終都是預期的結果。那么這個線程就是線程安全的。

      出現線程安全的三種情況

      在理解了上面的概念后,如果平時開發的時候就會發現,我們會經常遇到線程不安全的情況,大概的羅列了以下三種

      • 運行結果錯誤
      • 發布和初始化導致線程安全問題
      • 活躍性問題

      運行結果錯誤

      首先我們先看下面一段代碼

      public class WrongResult {
          private static volatile int count = 0;
          public static void main(String[] args) throws InterruptedException {
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      for (int i = 0; i < 1000; i++) {
                          count++;
                      }
                  }
              };
              Thread t0 = new Thread(runnable);
              Thread t1 = new Thread(runnable);
      
              t0.start();
              t1.start();
      
              t0.join();
              t1.join();
              System.out.println(count);
          }
      }
      

      上面這段代碼的邏輯,就是兩個線程對count進行自加一,每個線程都會循環自加1000次。那么預期的結果應該是2000。單其實執行的結果卻沒有2000,會始終比2000少,這究竟是什么原因導致這個種情況的發生呢。
      這里就需要理解我們計算機CUP調度的分配了。在CUP的分配規則里面,是以時間為單位給每個線程去分配的,如果當前線程的所分配的時間執行完,就會切給另外一個線程去執行,這樣不好的地方就是,他沒有辦法保證i + +的原子性,我們可以先看下面這張圖。
      image.png
      通過上面的圖我們可以了解到,i + +在cup執行的步驟其實分為三步

      • 第一步是讀取i的值
      • 第二步是執行加一操作
      • 第三步就是保存結果的值

      這樣我們就可以仔細的思考一下,如果CUP給線程一分配的時間只足夠他執行到第二步操作,之后就被切到了線程二,那么這時就會導致線程二沒有獲取到最新的值,因為線程一還沒有執行到第三步去保存結果就被CPU給切掉了。那么這個時候線程二再去自增計算出來的值,就會和線程一獲得的結果一樣的。自然我們拿到的結果就會比預期的結果少了很多。像這種情況也就是最典型的線程安全問題。

      發布和初始化導致線程安全問題

      這種就比較好理解,比如在項目啟動的時候,去獲取一段初始數據,但是這個數據需要通過線程異步的初始化才會有。但是線程初始化數據需要時間,如果程序在線程還沒有初始化完成數據后就去獲取數據,這個時候就會導致線程安全問題。可以看下面這段代碼來理解。

      public class WrongInit {
      
          private List<String> info;
      
          public WrongInit() {
              info = new ArrayList<>();
              Thread thread = new Thread(()->{
                 info.add("數據開始初始化");
                  try {
                      info.add("數據初始化中....");
                      //模擬數據初始化需要的時間
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                 info.add("數據初始化完成!");
              });
              thread.start();
          }
      
          public static void main(String[] args) throws InterruptedException {
              WrongInit init = new WrongInit();
              init.info.forEach(System.out::println);
          }
      }
      

      上面的代碼中,為了能模擬出數據沒有完全初始化的情況,在線程中休眠了1秒中,其實數據應該是要打印出【數據初始化完成!】然而結果去只打印到【數據初始化中....】,其實在這里還會有另外一種情況,如果創建線程的時間大于程序調用的時間,可能會直接報空指針異常。這種也是線程不安全的情況。

      活躍性問題

      線程的活躍性問題其實可以分為三種,分別為死鎖,活鎖和饑餓。

      這三種都有個一個共同的特性,就是他們會讓線程卡住,死活也得不到運行結果,這種情況其實是最線程安全中最復雜的也是最嚴重的,如果線程卡住的太多,不僅會占用服務的資源,甚至還會導致服務假死或者宕機。

      死鎖

      死鎖比較常見,就是兩個線程互相等單對方的資源,單同時又互不相讓,都想自己先執行。可以看下面這段代碼

      public class ThreadDeadMain {
      
          private static Object o1 = new Object();
      
          private static Object o2 = new Object();
      
          public static void main(String[] args) {
      
              Thread thread01 = new Thread(()->{
                 synchronized (o1){
                     System.out.println(Thread.currentThread().getName()+"獲取到o1的鎖了");
                     try {
                         TimeUnit.SECONDS.sleep(1);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     synchronized (o2){
                         System.out.println(Thread.currentThread().getName()+"獲取到o2的鎖了");
                     }
                 }
              });
      
              Thread thread02 = new Thread(()->{
                  synchronized (o2){
                      System.out.println(Thread.currentThread().getName()+"獲取到o2的鎖了");
                      try {
                          TimeUnit.SECONDS.sleep(1);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      synchronized (o1){
                          System.out.println(Thread.currentThread().getName()+"獲取到o1的鎖了");
                      }
                  }
              });
              thread01.start();
              thread02.start();
          }
      }
      

      上面這段代碼中啟動了兩個線程,每個線程中有兩個鎖,線程1啟動時會先獲取o1的鎖,然后再去獲取o2的鎖,之后才會執行完畢最后釋放o2和o1的鎖,線程2相對于線程1的邏輯則相反,顯示獲取o2的鎖,然后再去獲取o1的鎖,最后執行完畢釋放o2的鎖再去釋放o1的鎖。為了讓兩個線程能發生死鎖的情況,我在兩個線程都獲取第一個鎖的時候讓線程休眠的一秒種,這樣等到兩個線程同時去獲取第二個鎖的時候,就會發現,線程1的第二個鎖的o2在線程2的第一個鎖總沒有釋放,然而線程2的第二個鎖o1又被線程1占著,這樣就會發生兩者互不相讓,又同時占領著鎖。導致程序一直卡著。

      活鎖

      活鎖相對于死鎖有種相反的意思,死鎖是卡著資源,然后活鎖不一樣,他不占用鎖的資源,但是他會一直運行著,不過他會一直循環運行,但是一直沒有遇到正確的結果,導致線程一直在運行。但是他不會像死鎖一樣卡著不運行。

      可以看到下面這段代碼

      public class ThreadLiveMain implements Runnable {
      
          private int num;
      
          public ThreadLiveMain(int num) {
              this.num = num;
          }
      
          @Override
          public void run() {
              System.out.println("中獎數字:"+num);
              int i = 0;
              do {
                  //隨機獲取1-10的數字
                  Random random = new Random();
                  i = random.nextInt(10)+1;
                  System.out.println("隨機數:"+i);
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }while (num!=i);
              System.out.println("抽中啦!");
          }
          public static void main(String[] args) {
              ThreadLiveMain main = new ThreadLiveMain(11);
              Thread thread = new Thread(main);
              thread.start();
          }
      }
      

      這是一個類似于抽獎的小程序,線程里面會隨機出1-10的數字來判斷自己是否中獎,如果我們給中獎數字在1-10內,這個時候程序就會得到正常的結果,因為他在隨機數范圍內。但是如果我們給了一個11,這個時候就超出隨機數的范圍了。線程會一直的去循環判斷,但是又遇不到正確的隨機數字,這樣線程就不會停止下來,這種情況就稱之為活鎖。

      饑餓

      饑餓就比較有趣了,他就真的是因為饑餓所以才導致線程拿不到結果,Java的線程有優先級的概念,有1-10的優先級劃分,如果一個線程的等級被設置到最低的1,那么這個線程可能永遠也拿不到線程的資源,線程吃不到飯,自然也沒力氣干活,沒力氣干活那也就拿不到結果。還有一種情況就是某個線程持有某個文件的鎖,如果其他線程想有修改文件就需要先獲得這個文件的鎖,那這個修改文件的線程就會陷入饑餓,沒法在繼續運行。

      posted @ 2022-01-24 19:43  鄧小白  閱讀(184)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 叶城县| 男人的天堂va在线无码| 国产精品亚洲二区在线播放| 凤庆县| 九九热精品免费在线视频| 久久中文字幕一区二区| 亚洲色av天天天天天天| 日韩一区二区三区无码a片| 亚洲一区二区精品另类| 武宣县| 日韩无人区码卡1卡2卡| 成年女人喷潮免费视频 | 措美县| 欧美牲交a欧美牲交aⅴ一| 亚洲人亚洲人成电影网站色| 日韩高清国产中文字幕| 最新亚洲av日韩av二区| 综合图区亚洲另类偷窥| 一级做a爰片久久毛片下载| 国产精品视频第一第二区| 免费看无码自慰一区二区| 福利视频在线一区二区| av无码小缝喷白浆在线观看| 成人国产精品一区二区网站公司 | 亚洲综合区激情国产精品| 国产精品亚欧美一区二区三区| 国语精品自产拍在线观看网站| 亚洲欧美人成人让影院| 久久丫精品国产| 日本高清久久一区二区三区| 国产成人精品视频不卡| 中文字幕人妻日韩精品| 精品久久亚洲中文无码| 国产SM重味一区二区三区| 国产精品一亚洲av日韩| 国产成人人综合亚洲欧美丁香花| 成人片黄网站色大片免费毛片| 国产精品免费视频不卡| 在线观看免费人成视频色| 日韩精品人妻av一区二区三区| 午夜精品福利亚洲国产|