Java 中7種線程池詳解+示例代碼

一、線程池簡介

      周所周知,Java創建一個新線程的成本是比較高的。因此在面臨大量的多線程任務時,採用線程池幾乎成了慣用的做法,線程池其實也是設計模式中享元模式思想的一種應用。

     一般線程池剛啓動時會新建大量的(跟傳入參數有關)空閒線程,程序將一個Runnable或者Callable對象傳給線程池時,線程池會調用空閒線程執行他們的run()方法或者call()方法。執行完成後並不回收該線程,而是再次返回線程池中稱謂空閒線程,等待下一次任務。


二、Java線程池

     Java 5以前,開發者需要手動實現線程池,從Java 5 開始,Java支持了內建線程池,提供了一個Executors類來新建線程池,它位於java.util.concurrent包下,基本上所有跟併發編程相關的都在該包下面。

     Java 常用的線程池有7種,他們分別是:

          (1)newCachedThreadPool :創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

          (2)newFixedThreadPool:創建一個固定數目的、可重用的線程池。

          (3)newScheduledThreadPool:創建一個定長線程池,支持定時及週期性任務執行。

          (4)newSingleThreadExecutor:創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

          (5)newSingleThreadScheduledExcutor:創建一個單例線程池,定期或延時執行任務。

          (6)newWorkStealingPool:創建持有足夠線程的線程池來支持給定的並行級別,並通過使用多個隊列,減少競爭,它需要穿一個並行級別的參數,如果不傳,則被設定爲默認的CPU數量。

            (7) ForkJoinPool:支持大任務分解成小任務的線程池,這是Java8新增線程池,通常配合ForkJoinTask接口的子類RecursiveAction或RecursiveTask使用。


三、示例代碼(只介紹代表性的幾個)

    (1)newCachedThreadPool 會根據任務來臨的需要決定是否創建新的線程,也就是如果來了新任務又沒有空閒線程,它就會新建一個線程,下面用代碼可以理解這個事情。

package com;

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

public class Main {

	public static void main(String[] args) throws Exception {
		
		ExecutorService m=Executors.newCachedThreadPool();
		
		for(int i=1;i<=10;i++){
			final int count=i;
			
			m.submit(new Runnable(){
				@Override
				public void run() {
					System.out.println("線程:"+Thread.currentThread()+"負責了"+count+"次任務");
				}
				
			});
			//下面這行代碼註釋的話,線程池會新建10個線程,不註釋的話,因爲會複用老線程,不會產生10個線程
//			Thread.sleep(1);  
		}
		
	}
}


註釋掉Thread.sleep(1)的結果如下——產生了10個線程

線程:Thread[pool-1-thread-1,5,main]負責了1次任務
線程:Thread[pool-1-thread-2,5,main]負責了2次任務
線程:Thread[pool-1-thread-3,5,main]負責了3次任務
線程:Thread[pool-1-thread-4,5,main]負責了4次任務
線程:Thread[pool-1-thread-5,5,main]負責了5次任務
線程:Thread[pool-1-thread-6,5,main]負責了6次任務
線程:Thread[pool-1-thread-7,5,main]負責了7次任務
線程:Thread[pool-1-thread-8,5,main]負責了8次任務
線程:Thread[pool-1-thread-9,5,main]負責了9次任務
線程:Thread[pool-1-thread-10,5,main]負責了10次任務

不註釋掉Thread.sleep(1)的結果如下——產生了2個線程
線程:Thread[pool-1-thread-1,5,main]負責了1次任務
線程:Thread[pool-1-thread-2,5,main]負責了2次任務
線程:Thread[pool-1-thread-2,5,main]負責了3次任務
線程:Thread[pool-1-thread-2,5,main]負責了4次任務
線程:Thread[pool-1-thread-2,5,main]負責了5次任務
線程:Thread[pool-1-thread-2,5,main]負責了6次任務
線程:Thread[pool-1-thread-2,5,main]負責了7次任務
線程:Thread[pool-1-thread-2,5,main]負責了8次任務
線程:Thread[pool-1-thread-2,5,main]負責了9次任務
線程:Thread[pool-1-thread-2,5,main]負責了10次任務

    


(2)newFixedThreadPool 創建一個固定大小的、可重用是線程池

package com;

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

public class Main {

	public static void main(String[] args) throws Exception {
		
		ExecutorService m=Executors.newFixedThreadPool(4);
		
		for(int i=1;i<=10;i++){
			final int count=i;
			
			m.submit(new Runnable(){
				@Override
				public void run() {
					System.out.println("線程:"+Thread.currentThread()+"負責了"+count+"次任務");
				}
				
			});
    		Thread.sleep(1000);  
		}
		
	}
}

結果如下:

線程:Thread[pool-1-thread-1,5,main]負責了1次任務
線程:Thread[pool-1-thread-2,5,main]負責了2次任務
線程:Thread[pool-1-thread-3,5,main]負責了3次任務
線程:Thread[pool-1-thread-4,5,main]負責了4次任務
線程:Thread[pool-1-thread-1,5,main]負責了5次任務
線程:Thread[pool-1-thread-2,5,main]負責了6次任務
線程:Thread[pool-1-thread-3,5,main]負責了7次任務
線程:Thread[pool-1-thread-4,5,main]負責了8次任務
線程:Thread[pool-1-thread-1,5,main]負責了9次任務
線程:Thread[pool-1-thread-2,5,main]負責了10次任務

     


(3)newScheduledThreadPool創建一個定長線程池,支持定時及週期性任務執行。

package com;

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

public class Main {

	public static void main(String[] args) throws Exception {

		// 指定大小爲4
		ScheduledExecutorService m = Executors.newScheduledThreadPool(4);

		m.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				Date now = new Date();
				System.out.println("線程" + Thread.currentThread() + "報時:" + now);
			}

		}, 1, 1, TimeUnit.SECONDS); // 延遲1s秒執行,每隔1s執行一次

	}
}

結果

線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:15 CST 2015
線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:16 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:17 CST 2015
線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:18 CST 2015
線程Thread[pool-1-thread-1,5,main]報時:Thu Sep 10 14:55:19 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:20 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:21 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:22 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:23 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:24 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:25 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:26 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:27 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:28 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:29 CST 2015
線程Thread[pool-1-thread-2,5,main]報時:Thu Sep 10 14:55:30 CST 2015




(4)newWorkStealingPool創建一個帶並行級別的線程池,並行級別決定了同一時刻最多有多少個線程在執行,如不穿如並行級別參數,將默認爲當前系統的CPU個數。下面用代碼來體現這種並行的限制,從結果中可以看到,同一時刻只有兩個線程執行。

package com;

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

public class Main {

	public static void main(String[] args) throws Exception {

		// 設置並行級別爲2,即默認每時每刻只有2個線程同時執行
		ExecutorService m = Executors.newWorkStealingPool(2);

		for (int i = 1; i <= 10; i++) {
			final int count=i;
			m.submit(new Runnable() {
				@Override
				public void run() {
					Date now=new Date();
					System.out.println("線程" + Thread.currentThread() + "完成任務:"
							+ count+"   時間爲:"+	now.getSeconds());
					try {
						Thread.sleep(1000);//此任務耗時1s
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}

			});
           
		}
		while(true){
			//主線程陷入死循環,來觀察結果,否則是看不到結果的
		}
	}
}

結果:

線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:1   時間爲:7
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:2   時間爲:7
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:3   時間爲:8
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:4   時間爲:8
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:5   時間爲:9
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:6   時間爲:9
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:7   時間爲:10
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:8   時間爲:10
線程Thread[ForkJoinPool-1-worker-1,5,main]完成任務:9   時間爲:11
線程Thread[ForkJoinPool-1-worker-0,5,main]完成任務:10   時間爲:11


三、總結

     以上幾個線程池基本滿足了常用的線程池需求,開發者可根據場景靈活選中,其中可以實現大任務分解的ForkJoinPool線程池的介紹因爲篇幅較長,專門單獨寫了一篇博客,地址是: Java 多線程中的任務分解機制-ForkJoinPool詳解


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