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

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

    • 分享

      線程&線程池&死鎖定位

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

      以前我們知道創(chuàng)建線程的兩種方式:

      • 繼承Thread類;

      • 實現(xiàn)Runnable接口;

      本文再來講講另外兩種創(chuàng)建線程的方式:

      • 實現(xiàn)Callable接口;

      • 使用線程池

      callable接口


      1、有什么特點?
      Callable接口帶返回值。

      2、為什么要用Callable接口?
      舉個例子:

      我要計算從1加到10,再加上從11乘到15的結(jié)果,需要怎么做?如果是用一個線做,那就從頭算到尾,先算1加到10的結(jié)
      果,再加上后面乘法運算的結(jié)果。顯然乘法是比較費時間的,那么就可以創(chuàng)建一個新線程來計算11乘到15的結(jié)果。但是
      其他兩種創(chuàng)建線程的方式都是沒辦法拿到線程里面的返回值的,所以Callable接口出現(xiàn)了。

      3、怎么用?

      • 創(chuàng)建資源類實現(xiàn)Callable接口;

      • 重寫call方法;

      思考:Thread類的構(gòu)造只能接受Runnable接口,并不能接口Callable接口,怎么辦?
      解決:找中間人。如果有一個中間人同時實現(xiàn)了Runnable和Callable,那不就行了嘛。這就是適配器模式。這個中間人就是FutureTask實現(xiàn)類。

      接下來看看編碼實現(xiàn):

      class Resource implements Callable<Integer{

          @Override
          public Integer call() throws Exception {
              return 11 * 12 * 13 * 14 * 15;
          }
      }

      main方法:

       public static void main(String[] args) throws Exception{
              Resource callable = new Resource();
              FutureTask<Integer> futureTask = new FutureTask<>(callable);
              new Thread(futureTask, "AA").start();
              Integer result1 = 55// main線程計算出來的從 1 加到 10 的結(jié)果 
              Integer result2 = futureTask.get();
              System.out.println("result:\t" +  (result1 + result2));
       }

      計算出的結(jié)果為:

      運行結(jié)果

      這個結(jié)果是正確的。那么問題來了:

      • 如果還沒計算完 11 乘到 15 的值,我就通過get方法去取,會發(fā)生什么情況?看下面的代碼:

      class Resource implements Callable<Integer{

          @Override
          public Integer call() throws Exception {
              //System.out.println(Thread.currentThread().getName());
              TimeUnit.SECONDS.sleep(2); // 瞇一會兒
              return 11 * 12 * 13 * 14 * 15;
          }
      }
      public static void main(String[] args) throws Exception{
              Resource callable = new Resource();
              FutureTask<Integer> futureTask = new FutureTask<>(callable);
              new Thread(futureTask, "AA").start();
              Integer result2 = futureTask.get(); // 還沒計算完就取值

              System.out.println(Thread.currentThread().getName());
              System.out.println("hello");
              System.out.println("hello");

              Integer result1 = 55// main線程計算出來的從 1 加到 10 的結(jié)果
              System.out.println("result:\t" +  (result1 + result2));
       }

      大家注意看運行結(jié)果中的兩個 hello 是什么時候輸出的:

      運行結(jié)果


      可以看到,如果還沒計算完就取值,那么main線程就會被阻塞,直到計算完為止。這顯然違背了我們設(shè)計的初衷。

      • 我們使用Callable的目的就是:一個線程做這件事,另一個線程做另外一件事,在這兩個線程沒有需要用到對方的計算結(jié)果之前,互不干擾。其實這也是 Fork Join 的思想。

      也就是說,輸出兩個 hello 并不需要用到 call 方法 的返回值,所以即使還沒算完,也應(yīng)該可以正常輸出,而不是被阻塞。所以,get方法的調(diào)用要放在最后并且等計算完了再get。那么如何保證計算完了呢?看如下代碼:

      public static void main(String[] args) throws Exception{
              Resource callable = new Resource();
              FutureTask<Integer> futureTask = new FutureTask<>(callable);
              new Thread(futureTask, "AA").start();
              //new Thread(futureTask, "BB").start();
              Integer result1 = 55// main線程計算出來的從 1 加到 10 的結(jié)果

              System.out.println(Thread.currentThread().getName());
              System.out.println("hello");
              System.out.println("hello");

              while (!futureTask.isDone()){

              }
              Integer result2 = futureTask.get(); // get前先自旋

              System.out.println("result:\t" +  (result1 + result2));
       }

      把get方法的調(diào)用放在最后面,并且用while進行自旋操作,如果沒計算完,就會一直在while循環(huán)中。看看這次的運行結(jié)果:

      運行結(jié)果

      可以看到,這次main線程并沒被阻塞,運行后立即完成了自己該做的事。

      注意上面的call方法里有一行注釋掉的輸出語句,以及main方法里有一個注釋掉的線程BB。如果把注釋放開,其實也還是只有AA線程會進去,BB線程根本就調(diào)不到call方法。也就說,多個線程共用一個 futureTask,只會進去一次。


      線程池


      1、為什么要用線程池?
      線程池的工作就是控制運行的線程的數(shù)量,處理過程中將任務(wù)放入隊列,線程創(chuàng)建后就從任務(wù)隊列中取出來執(zhí)行任務(wù)。好處就是:線程復(fù)用,降低了資源消耗,提高了響應(yīng)速度、控制最大并發(fā)數(shù)、方便管理線程。

      2、如何使用線程池?

      • 架構(gòu):Java中的線程池是通過Executor框架實現(xiàn)的,創(chuàng)建線程池使用的是Executors工具類,底層使用的是ThreadPoolExecutor。

      • 常見的三種線程池:

       ExecutorService executor = Executors.newFixedThreadPool(3);
       ExecutorService executor1 = Executors.newSingleThreadExecutor();
       ExecutorService executor2 = Executors.newCachedThreadPool();

      第一個是線程池中線程數(shù)固定的,第二個就是線程池中只有一個線程,第三個就是帶緩存的,線程數(shù)量可變。這三個底層用的都是ThreadPoolExecutor。

      • 具體使用:

      public static void main(String[] args) throws Exception{
            ExecutorService executor = Executors.newFixedThreadPool(3);
            try {
                for (int i=0; i<10; i++){
                    executor.execute(() -> {
                        System.out.println(Thread.currentThread().getName());
                    });
                }
            }catch (Exception e){
            }finally {
                executor.shutdown();
            }
      }

      運行結(jié)果:

      運行結(jié)果


      可以看到,確實創(chuàng)建了三個線程。

      3、線程池的7大參數(shù):
      點進Executors創(chuàng)建線程池的源碼去看看:

      public static ExecutorService newFixedThreadPool(int nThreads) {
              return new ThreadPoolExecutor(nThreads, nThreads,
                                            0L, TimeUnit.MILLISECONDS,
                                            new LinkedBlockingQueue<Runnable>());
      }

      `
      發(fā)現(xiàn)只有5個參數(shù),那為什么說是7個參數(shù)呢?再點ThreadPoolExecutor進去看看:

      public ThreadPoolExecutor(int corePoolSize,
                                    int maximumPoolSize,
                                    long keepAliveTime,
                                    TimeUnit unit,
                                    BlockingQueue<Runnable> workQueue)
       
      {
              this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                   Executors.defaultThreadFactory(), defaultHandler);
      }

      可以發(fā)現(xiàn),最后兩個參數(shù)用的默認值,不需要我們傳,所以我們剛才只看到5個。下面來說說這7大參數(shù)都是干嘛的。

      • corePoolSize:線程池中的常駐核心線程數(shù)(銀行今日的當(dāng)值窗口數(shù))。

      • maximumPoolSize:線程池中能夠容納的最大線程數(shù)(這個銀行網(wǎng)點總共的窗口數(shù))。

      • keepAliveTime:多余空閑線程存活的時間。

      • unit:存活時間的單位。

      • workQueue:任務(wù)隊列(銀行的等待區(qū))。

      • threadFactory:創(chuàng)建線程池中線程的工廠。

      • handler:拒絕策略。

      那么這些參數(shù)到底什么意思呢?舉個生活當(dāng)中的例子讓你秒懂:

      今天星期天,你去銀行辦理業(yè)務(wù)。由于星期天,所以只開放了兩個窗口,所以corePoolSize就是2。如果剛好來了兩個人,那么能滿足需求。如果不止兩個人,那么其他人就得在等待區(qū)等著,這個等待區(qū)就是workQueue。如果等待區(qū)也坐滿了人,那么當(dāng)值人員就會打電話給經(jīng)理,讓經(jīng)理叫人加班多開窗口,假如這個銀行總共有5個窗口,那么這個5就是maximumPoolSize。如果開了5個窗口還是忙不過來,等待區(qū)還是爆滿,那么大堂經(jīng)理就會對辦理業(yè)務(wù)的人說:不好意思,我們這里忙不過來了,請您去別的網(wǎng)點吧。這就是拒絕策略。當(dāng)辦業(yè)務(wù)的人慢慢的少了,來加班的那幾個窗口如果超過了keepAliveTime時間都還沒有人來辦理業(yè)務(wù),那么他們就會下班。也就是說,兩個窗口忙得過來就不會勞煩別人加班。

      4、線程池的拒絕策略:
      上面說到了,如果全部窗口開放了,等待區(qū)也滿了,還有人來的話,大堂經(jīng)理就會使用拒絕策略。線程池有四種拒絕策略:

      • AbortPolicy(默認):拋異常。

      • CallerRunsPolicy:將任務(wù)回退到調(diào)用者。

      • DiscardOldestPolicy:拋棄等待最久的任務(wù)。

      • DiscardPolicy:丟棄任務(wù)。

      5、上面說到三個最常見的線程池,生產(chǎn)中使用哪個?
      答案是,哪個都不用。也就是說,在實際開發(fā)中,不要使用Executors創(chuàng)建線程池。為什么?

      因為FixedThreadPool和 SingleThreadExecutor 底層用的阻塞隊列是 LinkedBlockingQueue,這個隊列是有界,但是最大值是 int 的最大值,21億多,相當(dāng)于無界。也就是說可能會在這里堆積大量的任務(wù),造成OOM。CachedThreadPool本身線程數(shù)就可變,允許的最大線程數(shù)也是 int 的最大值,所以可能會創(chuàng)建大量的線程,最終造成OOM。

      既然都不用, 那我們?nèi)绾蝿?chuàng)建線程池?答案是我們使用 ThreadPoolExecutor,手動配置那7個參數(shù)。如下:

      public static void main(String[] args{
              ExecutorService threadPool = new ThreadPoolExecutor(
                              2// corePoolSize
                              5// maximumPoolSize
                              1// keepAliveTime
                              TimeUnit.SECONDS, // unit
                              new LinkedBlockingQueue<>(3), // workQueue
                              Executors.defaultThreadFactory(), // threadFactory
                              new ThreadPoolExecutor.AbortPolicy()); // handler
              try {
                  // 10 個請求
                  for (int i=1; i<=10; i++) {
                      threadPool.execute(() -> {
                          System.out.println(Thread.currentThread().getName() + "\t 辦理業(yè)務(wù)");
                      });
                  }
              } catch (Exception e) {
              }finally {
                  threadPool.shutdown();
              }
      }

      既然我們說人家Executors創(chuàng)建的線程池不行,那么我們創(chuàng)建的怎么就行呢?那些參數(shù)怎么來的?corePoolSize設(shè)置為多少、maximumPoolSize又設(shè)置為多少才合適?請看下面的線程池參數(shù)配置。

      6、線程池參數(shù)配置:
      說參數(shù)配置前,先來了解兩個概念:

      • IO密集型:所謂IO,就是對磁盤進行讀寫操作。IO密集型就是CPU的運行速度很快,而磁盤讀寫能力較弱。所以在讀寫數(shù)據(jù)的時候,CPU游刃有余,CPU占用率不會很高。

      • CPU密集型:CPU密集型就是CPU處理能力不太行,而磁盤讀寫能力很強。所以在讀寫的時候,可能出現(xiàn)CPU占用率100%的情況。

      CPU密集型要盡可能的減少線程數(shù)量,一般公式:

      最大線程數(shù) = CPU核數(shù) + 1

      IO密集型則應(yīng)盡可能多的配置線程,一般公式:

      最大線程數(shù) = CPU核數(shù) * 2 
      或者
      最大線程數(shù) = CPU核數(shù) / (1 - 0.9)

      獲取CPU核心數(shù)的方式:

      Runtime.getRuntime().availableProcessors()

      死鎖問題


      1、什么是死鎖?
      官方解釋:兩個或兩個以上的線程在執(zhí)行過程中因爭奪資源而產(chǎn)生相互等待的現(xiàn)象。
      一句話講明白:吃著碗里的,想著鍋里的。再說明白一點,線程1持有鎖A,卻還想著去拿鎖B,線程2持有鎖B,卻想著拿鎖A。

      2、寫一個死鎖:

      class DeadLock implements Runnable{
          private String lockA;
          private String lockB;
          public DeadLock(String lockA, String lockB) {
              this.lockA = lockA;
              this.lockB = lockB;
          }
          @Override
          public void run() {
              synchronized(lockA) {
                  System.out.println(Thread.currentThread().getName() + "持有鎖" + lockA + ",嘗試獲取鎖" + lockB);
                  synchronized (lockB) {
                      System.out.println(Thread.currentThread().getName() + "持有鎖" + lockB + ",嘗試獲取鎖" + lockB);
                  }
              }
          }
      }

      測試:

      public static void main(String[] args) {
              String lockA = "lockA";
              String lockB = "lockB";
              new java.lang.Thread(new DeadLock(lockA, lockB), "線程1").start();
              new java.lang.Thread(new DeadLock(lockB, lockA), "線程2").start();
      }

      看運行結(jié)果:

      運行結(jié)果


      可以看到,運行根本停不下來。

      3、死鎖定位分析:
      出現(xiàn)上面這種情況,我們怎么知道是死鎖造成的呢?也許是死循環(huán)呢!給個讓人信服的理由!
      我們知道Linux中有這樣一條命令:

      ps -ef | grep xxx

      這條命令可以查看進程號,Java也提供了類似的命令,那就是:

      jps -l

      用這條命令查看到Java進程號后,找到可能出現(xiàn)異常的進程,再輸入:

      jstack 進程號

      這樣就可以查看到詳細信息了。以上面的代碼為例,先在cmd中進入項目路徑,輸入上面的命令。

      執(zhí)行結(jié)果

      這樣就說明這是死鎖了。



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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多