java多線程:使用newFixedThreadPool方法創建指定線程數量的線程池

寫在前面的話:本文給出瞭如何創建一個有界線程池的一種方法,並對其中的問題進行了分析理解,其中最後一個分析問題個人覺得非常有價值,通過這個問題能幫我們更好的理解線程池。

1.創建無界線程池可能會造成的問題

在上一篇博客中我們對線程池有一個簡單的瞭解,並知道了如何創建線程池以及讓線程池中的線程執行。但是上一篇博客中所用的newCachedThreadPool()方法創建線程並不好,因爲如果使用不當會造成內存溢出異常。參見博客:https://blog.csdn.net/wenniuwuren/article/details/51700080

2.如何創建指定線程數量的線程池?

代碼如下:

package com.springboot.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestMyFixedThreadPool {
    public static void main(String[] args) {
        /*創建指定數量的線程池*/
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(new MyRunnable("小明"));
        executorService.execute(new MyRunnable("小華"));
        executorService.execute(new MyRunnable("李雷"));
        executorService.execute(new MyRunnable("韓梅梅"));
        executorService.execute(new MyRunnable("小王"));
    }
}


class MyRunnable implements Runnable{
   private String userName;

   MyRunnable(String userName){
       this.userName=userName;
   }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
            Thread.sleep(300l);
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

pool-1-thread-2 userName=小華 begin 1572234540656
pool-1-thread-1 userName=小明 begin 1572234540656
pool-1-thread-3 userName=李雷 begin 1572234540681
pool-1-thread-2 userName=小華 end 1572234540956
pool-1-thread-1 userName=小明 end 1572234540957
pool-1-thread-1 userName=韓梅梅 begin 1572234540958
pool-1-thread-2 userName=小王 begin 1572234540958
pool-1-thread-3 userName=李雷 end 1572234540981
pool-1-thread-2 userName=小王 end 1572234541258
pool-1-thread-1 userName=韓梅梅 end 1572234541258

從運行結果可以看出,由於我們一開始的時候指定線程池大小是3,但是加入了5個任務,所以開始時候只能有3個任務在運行,當線程池中的線程有空閒時立馬接受新的任務並開始執行,從運行結果上面我們也可以看出他們在時間是連續的:即上一個任務的結束時間即爲下一個任務的開始時間。

3.引入問題分析:當線程池中的線程把任務執行完以後它們還在繼續運行嗎?

如果我們沒有試驗或者沒有深入瞭解線程池的話,我們可能會這樣回答,都執行完成任務了還執行個啥啊?爲了節省資源肯定是不運行。(語氣肯定,信心滿滿)但是實際上卻是這樣的:

如圖,我們可以看到此時任務全部執行完了,但是程序依然在運行,說明仍然有線程在運行。說明我們的臆想出來的答案是錯誤的。所以新的思考隨之 而來:到底是什麼線程在運行呢?我的猜想是這樣:當我線程開始執行以後會隨之創建一個新的守護線程,除非認爲關閉否則他將一直在運行,從而導致程序一直在運行,至於線程池中的線程我也認爲是任務結束以後將會死亡。但是隻有猜想沒有驗證是肯定行不通的,上一個猜想才讓我被打臉。

驗證猜想:使用java虛擬機提供的管理工具來進行分析,打開管理工具(打開命令行窗口輸入:jvisualvm命令)

 紅色標註的地方爲當前正在運行的程序,點開以後我們可以看到此程序所包含的線程。

我們驚奇的發現,我創建線程池的時候指定的三個線程它們依然還在,狀態爲“駐留”狀態。那爲什麼是這樣的呢?

看源碼:

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

此方法的第一個和第三個參數分別爲:corePoolSize、keepAliveTime

繼續進入ThreadPoolExecutor類:

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

看到這兩參數的註釋:

corePoolSize:the number of threads to keep in the pool, even if they are idle.【除非設置了{@code allowCoreThreadTimeOut},即使它們處於空閒狀態也要保留在池中的線程數】

keepAliveTime:when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.【當線程數大於內核數時,這是多餘的空閒線程將在終止之前等待新任務的最長時間。】

我們可以反過來想:如果線程的數量沒有超過內核數,就算是空閒線程他也將一直處於運行狀態。

 

此時又有一個新問題,是不是當我們創建一個指定數量的線程池的時候,就已經有指定數量的在線程池中運行呢?

通過代碼來驗證:

package com.springboot.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestMyFixedThreadPool {
    public static void main(String[] args) {
        /*創建指定數量的線程池*/
        ExecutorService executorService = Executors.newFixedThreadPool(3);
      
    }
}


class MyRunnable implements Runnable{
   private String userName;

   MyRunnable(String userName){
       this.userName=userName;
   }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
            Thread.sleep(300l);
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

再看java虛擬機:

之前的程序不在了。

執行一個任務:

package com.springboot.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestMyFixedThreadPool {
    public static void main(String[] args) {
        /*創建指定數量的線程池*/
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        executorService.execute(new MyRunnable("小明"));
       /* executorService.execute(new MyRunnable("小華"));
        executorService.execute(new MyRunnable("李雷"));
        executorService.execute(new MyRunnable("韓梅梅"));
        executorService.execute(new MyRunnable("小王"));*/
    }
}


class MyRunnable implements Runnable{
   private String userName;

   MyRunnable(String userName){
       this.userName=userName;
   }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" begin "+System.currentTimeMillis());
            Thread.sleep(300l);
            System.out.println(Thread.currentThread().getName()+" userName="+userName+" end "+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結果:

任務執行完,程序依然在運行。

我們可以看到,此時有一個線程在運行,導致程序在運行。 

至此我們可以得出結論:

       當我們創建一個指定數量的線程池的時候,會初始化一個線程指定大小線程池,但是並沒有線程在運行,只有當我們調用executorService.execute();方法以後纔會有線程加入線程池,並且當他執行完任務以後(處於空閒狀態)它不會被移除線程池(前提:線程數沒有超過核心線程池大小,如果線程數超過內核數的話他將會在超過keepAliveTime以後消失),而是一直處於駐留狀態,同時實際線程的數量遵行這樣的原則:如果線程數小於線程池的大小以實際線程數量爲準,最大隻能和線程池大小一樣。【注:本文精華】

 

參考文獻:《java併發編程核心框架與方法》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章