對 Java 四種線程池的簡要分析

1 談談線程池

在之前,我們在需要使用線程的時候就去創建一個線程,可謂是非常簡便了。但是,當併發的線程數量多了之後,頻繁創建線程會令系統的效率大大下降。

那有沒有辦法可以令我們複用線程?線程在執行完一個任務之後,不會銷燬,進而執行其他的任務呢?

事實上,我們可以通過線程池實現相應的功能。在 Java 中,線程池的頂級接口是 Executor,但它並不是線程池的具體實現,真正的線程池實現類爲 ThreadPoolExecutor。

我們可以向線程池中傳遞任務以獲得執行,可傳遞的任務有以下兩種,分別是通過 Runnable 實現的任務與通過 Callable 實現的任務,這兩者之間的區別爲 Runnable 沒有返回值但 Callable 有返回值。

2 四種線程池

在 Java 提供了四種線程池的具體實現,分別如下:

  1. newCachedThreadPool:創建一個可緩存線程池
  2. newFixedThreadPool:創建一個定長線程池
  3. newScheduledThreadPool:創建一個定長線程池
  4. newSingleThreadExecutor:創建一個單線程化的線程池

下面對這四種線程池進行簡單介紹。

newCachedThreadPool

newCachedThreadPool 是一個可緩存線程池。當調用 execute 方法時,會重用以前構造的線程。該線程池的線程數量並不固定,且線程數量的最大值爲 Integer.MAX_VALUE。線程池中的空閒線程有超時限制,這個時間爲60秒,超過60秒閒置線程就會被回收。

該線程池適合執行大量耗時較少的任務,當線程池處於閒置狀態時,線程池中的所有線程都會因爲超時從而被回收。

package test;

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

public class ThreadPoolTest {

	public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		
		for(int i = 0;i < 10;i++) {
			int index = i;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			cachedThreadPool.execute(() -> System.out.println("第" + index + "個任務,當前執行任務的線程爲:" + Thread.currentThread().getName()));
		}
	}
}

結果如下:
在這裏插入圖片描述
我們可以發現,事實上執行我們任務的都是同一個線程,這是因爲當我們執行下一個任務時,上一個任務已經執行完成,可以複用上一個任務使用的線程,不需要新建線程。

newFixedThreadPool

newFixedThreadPool 是一個定長線程池,該線程池可以指定工作線程數量,每當我們提交一個任務時,就會創建一個工作線程,且當工作線程處於空閒狀態時並不會被回收。當工作線程的數量超過最大值時,會將提交的任務存放進沒有大小限制的隊列中。

newFixedThreadPool 只有核心線程,且這些核心線程並不會被回收,故其能夠快速響應外界請求。

package test;

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

public class ThreadPoolTest {

	public static void main(String[] args) {
		//最大線程個數爲3
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
		
		for(int i = 0;i < 10;i++) {
			int index = i;
			fixedThreadPool.execute(() -> {
				System.out.println("第" + index + "個任務,當前執行任務的線程爲:" + Thread.currentThread().getName());
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
	}
}

結果如下:
在這裏插入圖片描述
我們可以發現,由於我們設置的最大線程爲3,故當線程池的工作線程等於3個時,即使有新任務進來且無空閒線程,也不會新建線程,而是會將其存放入隊列中等待工作線程進行消費。

newScheduledThreadPool

newScheduledThreadPool 是一個定長線程池,它的核心線程數量是固定的,而非核心線程數是沒有限制的,且當非核心線程閒置時會被回收。

newScheduledThreadPool 可以延遲運行任務或定時執行任務,適合於執行定時任務或執行週期性任務。

package test;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest {

	public static void main(String[] args) {
		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
		System.out.println("開始時間" + System.currentTimeMillis());
		scheduledThreadPool.schedule(() -> System.out.println("執行任務的時間" + System.currentTimeMillis()),5,TimeUnit.SECONDS);
	}
}

結果如下:
在這裏插入圖片描述
newScheduledThreadPool 確實起到延時執行任務的作用。

newSingleThreadExecutor

newSingleThreadExecutor 是一個單線程化的線程池。該線程池內部只有一個核心線程,任務會存放進一個無界隊列中讓該線程順序執行。newSingleThreadExecutor 的特點就是確保所有任務在同一線程內順序執行。

package test;

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

public class ThreadPoolTest {

	public static void main(String[] args) {
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		for(int i = 0;i < 10;i++) {
			int index = i;
			singleThreadExecutor.execute(() -> System.out.println("第" + index + "個任務被執行")); 
		}
	}
}

結果如下:
在這裏插入圖片描述
可見 newSingleThreadExecutor 保證了上述線程的順序化執行。

3 線程池的原理

看到這裏,可能有的讀者會非常疑惑。咦,博主,你之前不是說真正的線程池實現類爲 ThreadPoolExecutor 嗎,我看你介紹了四種線程池,怎麼就沒提到 ThreadPoolExecutor 呢?別急,且聽我慢慢解釋。

我們以 newCachedThreadPool 爲例子進行講解,當你執行下面這行代碼時

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

其實真正執行的是 ThreadPoolExecutor 的構造方法,換言之,四種線程池底層都是通過 ThreadPoolExecutor 來進行初始化的。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

其它三種線程池的實現如下:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
	public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    
    //ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor且實現了ScheduledExecutorService接口
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

那我們再來看看在 newCachedThreadPool 中調用的 ThreadPoolExecutor 的構造函數

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

它真正調用的是這個構造函數

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

每個參數的具體含義如下:

  1. corePoolSize:核心池大小,當線程池的線程數量超過了這個值,會將新的任務放置在等待隊列中
  2. maximumPoolSize:線程池最大線程數量,即線程池能創建的最大線程數
  3. keepAlivertime:當活躍線程數大於核心線程數時,多餘線程的最大存活時間
  4. unit:存活時間的單位
  5. workQueue:一個存放任務的阻塞隊列
  6. threadFactory:線程工廠,用來創建線程
  7. handler:當線程池的任務緩存隊列已滿並且線程池中的線程數目達到 maximumPoolSize 且仍有任務到來時執行的任務拒絕策略

4 線程池的優點

  1. 線程池可以重用線程,以避免線程的頻繁創建和銷燬帶來的性能開銷
  2. 可以有效控制線程池的併發數,有效避免大量的線程爭奪 CPU 資源而造成堵塞
  3. 線程池可以對線程進行管理,例如可以提供定時、定期、單線程、併發數控制等功能
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章