首頁  >  文章  >  Java  >  Java中關於線程池使用和原理的詳解

Java中關於線程池使用和原理的詳解

黄舟
黄舟原創
2017-10-09 09:58:261478瀏覽

這篇文章主要為大家詳細介紹了Java線程池使用與原理的相關資料,具有一定的參考價值,有興趣的小伙伴們可以參考一下

線程池是什麼?

我們可以利用java很容易創建一個新線程,同時作業系統創建一個線程也是一筆不小的開銷。所以基於線程的複用,就提出了線程池的概念,我們使用線程池創建出若干個線程,執行完一個任務後,該線程會存在一段時間(用戶可以設定空閒線程的存活時間,後面會介紹),等到新任務來的時候就直接重複使用這個空閒線程,這樣就省去了創建、銷毀線程損耗。當然空閒線程也會是一種資源的浪費(所有才有空閒線程存活時間的限制),但總比頻繁的創建銷毀線程好太多。
以下是我的測試程式碼


  /*
   * @TODO 线程池测试
   */
  @Test
  public void threadPool(){

    /*java提供的统计线程运行数,一开始设置其值为50000,每一个线程任务执行完
     * 调用CountDownLatch#coutDown()方法(其实就是自减1)
     * 当所有的线程都执行完其值就为0
    */
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    Executor pool = Executors.newFixedThreadPool(10);//开启线程池最多会创建10个线程
    for(int i=0;i<50000;i++){
      pool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50个线程都执行完了,共用时:"+(end-start)+"ms");
  }


  /**
   *@TODO 手动创建线程测试 
   */
  @Test
  public void thread(){
    CountDownLatch count = new CountDownLatch(50000);
    long start = System.currentTimeMillis();
    for(int i=0;i<50000;i++){
      Thread thread = new Thread(new Runnable() {

        @Override
        public void run() {
          System.out.println("hello");
          count.countDown();
        }
      });
      thread.start();
    }

    while(count.getCount()!=0){//堵塞等待5w个线程运行完毕

    }
    long end = System.currentTimeMillis();
    System.out.println("50000个线程都执行完了,共用时:"+(end-start)+"ms");


  }

使用執行緒池5w執行緒運行完大約為400ms,不使用執行緒池運行大約為4350ms左右,其效率可見一斑(讀者可以自行測試,不過由於電腦配置不一樣,跑出來的數據會有差別,但使用線程池絕對是比創建線程要快的)。

java如何使用執行緒池?

上面的測試程式碼中已經使用了執行緒池,下面正式介紹一下。

java所有的線程池最頂層是一個Executor接口,其只有一個execute方法,用於執行所有的任務,java又提供了ExecutorService接口繼承自Executor並且擴充了一下方法,在往下就是AbstractExecutorService這個抽象類,其實現了ExecutorService,最後就是ThreadPoolExecutor其繼承自上面的抽象類,我們常使用的java線程池就是創建的這個類別的實例。

而上面我們使用Executors是一個工具類,它就是一個語法糖,為我們把各種不同的業務的線程池參數進行封裝,進行new操作。


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

上面就是Executors.newFixedThreadPool(10)的原始碼。

下面重點來了,說一說ThreadPoolExecutor建構方法各參數的意思。


 public ThreadPoolExecutor(int corePoolSize,
               int maximumPoolSize,
               long keepAliveTime,
               TimeUnit unit,
               BlockingQueue<Runnable> workQueue,
               ThreadFactory threadFactory,
               RejectedExecutionHandler handler)

上面這個建構方法是最全的。

下面我們根據原始碼來解釋部分參數意思,這樣更有說服力。

下面是ThreadPoolExecutor#execute方法,就是我們上面介面所呼叫的execute實際執行者。


 public void execute(Runnable command) {
    if (command == null)
      throw new NullPointerException();

    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
        return;
      c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
      if (! isRunning(recheck) && remove(command))
        reject(command);
      else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
    }
    else if (!addWorker(command, false))
      reject(command);
  }

ctl是一個AtomicInteger實例,是一個提供了原子語句的CAS操作的類,它用來記錄線程池中當前運行的線程數量加上-2 ^29,workCountOf方法就取得其絕對值(可以去看原始碼如何實作),當其小於corePoolSize時,就會呼叫addWorker方法(是用來建立一個新Workder,Workder會建立一個Thread,所以就是建立執行緒的方法),addWorkd建立執行緒過程中會跟corePoolSize或maxnumPoolSize的值比較(當傳入true會根corePoolSize比較,false會根據maxnumPoolSize比較,大於等於其值會建立失敗)。可見如何當前運行中的線程數量小於corePoolSize就是創建並且也會創建成功(
只簡單的討論線程池Running狀態下)。

如果當運行中線程數大於等於corePoolSize時,進入第二個if,isRunning是跟SHUTDOWN(其值=0)比較,之前說過c等於當前運行的線程數量加上-2^ 29,如果目前目前運行的執行緒資料達到2^29時其值就=0,isRunning回傳false,else中在執行addWorkd也會回傳false(addWorkd也對其進行了檢驗),所以這表示執行緒池最多能支援2^29個執行緒同時運行(夠用了)。

workQueue.offer(command)就是將runnable加入等待佇列,加入等待佇列後runWorker方法會從佇列中取得任務執行的。如果目前佇列採用的是有界佇列(ArrayBlockingQueue)當佇列滿了offer就會回傳false,這是就進入else if,看!這裡傳入了false,說明這裡要跟maxnumPoolSize比較了,如果這裡執行的執行緒數大於等於maxnumPoolSize,那麼這個執行緒任務就要被執行緒池拒絕了,執行reject(command),拒絕方法中使用了我們ThreadPoolExecutor建構方法中的RejectedExecutionHandler(拒絕策略),後面再詳細解釋。

經過上面的結合原始碼的介紹,下面對們ThreadPoolExecutor的參數介紹就好理解了。

線程池中線程創建和拒絕策略

corePoolSize,maxnumPoolSize,BlockingQueue這三個要一塊說

當執行緒池運行的執行緒小於corePoolSize時,來一個新執行緒任務總是會新建一個執行緒來執行;當大於corePoolSize就會把任務加入到等待佇列blockingQueue中,如果你傳入的BlockingQueue是一個無界佇列(LinkedBlockingQueue)這是佇列可以存放「無限多」的任務,所有總是會加入佇列成功,跟maxnumPoolSize就沒關係了,這也表示執行緒池中執行緒數最多為corePoolSize個;但是如果你傳入的是有界隊列(ArrayBlockingQueue,SynchronousQueue),當佇列滿時,且執行緒數小於maxmunPoolSize就是建立新的執行緒直到執行緒數大於maxnumPoolSize;如果當執行緒數量大於maxnumPoolSize時,在​​加入任務就會被執行緒池拒絕。

RejectedExecutionHandler拒絕策略java給實作了4個AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy,DiscardPolicy使用者也可以自己實作該介面實作自己的拒絕策略;第一個就是直接拋出例外,我們可以進行trycatch處理;第二個是該新任務直接運作;第三個是取消佇列中最老的;第四個是取消目前任務。

以上是Java中關於線程池使用和原理的詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn