常用線程池的創建方式及內部隊列

常用的線程池創建

一、爲什麼要使用線程池

1.線程池多數使用在高併發的情況下,使用線程池可以重複利用已經創建的線程,減少了線程創建和銷燬時的資源消耗;
2.由於沒有頻繁創建和銷燬線程,使得系統效率可以大大提升;
3.可以控制線程數量,更有效的利用系統資源;

二、線程池執行流程

三、常用創建方式

先了解下線程池比較重要的幾個參數

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

【int corePoolSize】核心線程數
【int maximumPoolSize】允許的最大線程數 
【long keepAliveTime】空閒線程存活時間
【TimeUnit unit】keepAliveTime參數的時間單位
【BlockingQueue<Runnable> workQueue】任務隊列,線程池達到了核心線程數,其他線程又是活躍狀態,任務進入此隊列
【ThreadFactory threadFactory】定製線程的創建過程
【RejectedExecutionHandler handler】拒絕策略,  當workQueue隊滿時,採取的措施

1.CachedThreadPool

       調用ThreadPoolExecutor的構造,設置最大線程數爲Integer最大值,無限制的線程池,理論上,如果其他線程均爲活躍狀態,並非空閒,只要接到任務需求,就會創建新線程,所以相對FixedThreadPool更快一點。

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

如何創建:

public class CachedThreadPoolTest {
	
	public static void main(String[] args) {
		ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			cacheThreadPool.execute(new Runnable(){
				@Override
				public void run(){
					System.out.println(Thread.currentThread().getName() 
                                                       + ":" + System.currentTimeMillis());
					//每條線程執行至少10ms,則會創建10條線程
					try {
						TimeUnit.MILLISECONDS.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}		
	}	
}

執行結果:

對比代碼: 

public class CachedThreadPoolTest {
	
	public static void main(String[] args) {
		ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			cacheThreadPool.execute(new Runnable(){
				@Override
				public void run(){
					System.out.println(Thread.currentThread().getName() 
                                                       + ":" + System.currentTimeMillis());
				}
			});
			//循環啓動十次任務,但線程已經執行完畢,一直是同一條線程執行任務
			try {
				TimeUnit.MILLISECONDS.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
    }
}

執行結果:

 2.FixedThreadPool

    啓動固定線程數量的線程池,調用ThreadPoolExecutor的構造,將corePoolSize和maximumPoolSize設置相同值,使線程數量固定爲給定參數值,空餘線程等待時間設置爲0,無界任務隊列LinkedBlockingQueue,數量固定,即使沒有任務,空閒的線程也不會被回收。

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

如何創建:

public class FixedThreadPoolTest {
	public static final int FLAG = 15;
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
		for (int i = 0; i < 10; i++) {
			Future<?> result = newFixedThreadPool.submit(new Runnable(){
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+ ":" + FLAG);
					try {
						TimeUnit.SECONDS.sleep(2);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
	}
}

執行結果:

3. SingleThreadPool:

   創建單一線程池:只有一條線程,調用ThreadPoolExecutor構造, 將coreThreadSize及maximumPoolSize設置爲1,後續線程任務將在任務隊列中排隊。

//源碼
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

如何創建:

public class SingleThreadPoolExecutor {
	public static void main(String[] args) {
		ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10; i++) {
			singleThreadPool.execute(new Runnable(){
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() 
							+ ":" + System.currentTimeMillis());
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
	}
}

 執行結果:

 4.ScheduledThreadPool

   可以實現延遲執行或定時執行任務。

如何創建延時任務

public class ScheduledThreadPoolTest {
	public static void main(String[] args) {
		ScheduledExecutorService scheduleExecutorService = Executors.newScheduledThreadPool(3);
		//延時執行:
		System.out.println(System.currentTimeMillis());
		scheduleExecutorService.schedule(new Runnable(){
			@Override
			public void run() {
				System.out.println(System.currentTimeMillis());
			}
		}, 3, TimeUnit.SECONDS);//延遲3秒執行
}

執行結果:

如何創建延時定時任務 

public class ScheduledThreadPoolTest {
	public static void main(String[] args) {
		ScheduledExecutorService scheduleExecutorService = Executors.newScheduledThreadPool(3);
		//延遲加定時執行
		System.out.println(System.currentTimeMillis());
		scheduleExecutorService.scheduleAtFixedRate(new Runnable(){
			@Override
			public void run() {
				System.out.println(System.currentTimeMillis());
			}
		}, 1, 3, TimeUnit.SECONDS);//延遲1秒,每三秒執行一次
	}
}

執行結果:

5. ForkJoinPool

   分段執行線程任務,多用於單個線程任務有參數限制,需要進行多段任務同時進行,以提高效率的情況。

代碼演示:

public class ForkJoinPoolTest {

	public static void main(String[] args) {
		int[] arr = new int[120];
		int total = 0;
		for (int i = 0; i < arr.length; i++) {
			arr[i] = (int)(Math.random()*20);
			total += arr[i];
		}
		System.out.println("初始化120個數字累計和爲:" + total);
		
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		SumTask task = new SumTask(arr, 0, arr.length);
		Future<Integer> future = forkJoinPool.submit(task);
		try {
			System.out.println("分段計算120個數字累計和爲:" + future.get());
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
		
	}

}
class SumTask extends RecursiveTask<Integer>{
	
	private static final int THRESHOLD = 50;//最多允許計算50個數
	private int[] arr;
	private int start;
	private int end;
	
	public SumTask(int[] arr, int start, int end) {
		this.arr = arr;
		this.start = start;
		this.end = end;
	}

	@Override
	protected Integer compute() {
		Integer sum = 0;
		if( end - start < THRESHOLD){
			for (int i = start; i< end; i++) {
				sum += arr[i];
			}
			return sum;
		}else{
			int middle = (end + start)/2;
			SumTask left = new SumTask(arr,start,middle);
			SumTask right = new SumTask(arr,middle,end);
			left.fork();
			right.fork();
			return left.join() + right.join();
		}
	}
	
}

執行結果:

6. WorkStealingPool

   可以設置並行數量的線程池,不設置參數則默認爲電腦的CPU線程數量。

如何創建:

public class WorkStealingPoolTest {

	public static void main(String[] args) {
		//設置並行數量爲2,最多兩個線程執行任務
		ExecutorService workStealingPool = Executors.newWorkStealingPool(2);
		for (int i = 0; i < 10; i++) {
			final int count = i; 
			workStealingPool.submit(new Runnable(){
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() 
							+ "正在執行任務" + count + ",時間:" 
							+ System.currentTimeMillis());
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
		//程序延遲10秒結束,以觀察結果
		try {
			TimeUnit.SECONDS.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

執行結果:

 四、線程池內部任務隊列

1.ArrayBlockingQueque:
   基於數組實現的阻塞隊列,內部維護了一個定長數組,以便緩存隊列中的數據對象,內部還保存兩個整形變量,在生產者生產數據和消費者獲取數據的過程中,沒有進行鎖分離,用的是同一把鎖,因此生產者和消費者無法並行,效率相對較低,在初始化時數組必須指定長度,即必須指定隊列大小。

2.LinkedBlockingQueue:
   無邊界鏈式阻塞隊列,用在【FixedThreadPool】及【SingleThreadPool】內部,其內部使用鏈表實現,因爲生產者和消費者使用的是獨立的鎖對象,生產用的是【putLock】消費用的是【takeLock】,所以能夠更加高效的處理併發數據,生產者和消費者可以並行操作隊列中的數據,提高併發性能,初始化過程中可以不指定隊列大小,默認是Integer最大值。

3.DelayedWorkQueue:
    
延時阻塞隊列 ,用在【ScheduledThreadPool】內部,只有當延時的時間到了,纔會從隊列中獲取元素,因此,再向隊列中插入數據的操作(即生產者)永遠不會被阻塞,只有消費者在獲取數據的時候,可能被阻塞。

4.PriorityBlockingQueue:
   基於優先級的阻塞隊列,優先級通過傳入的參數決定,該隊列不會阻塞生產者,只有在消費者沒有數據可以取的時候,阻塞消費者,因此,該隊列的生產者插入數據的速度,必須小於消費者獲取數據的速度,否則時間長會耗盡所有可用的堆內存,內部控制線程同步時,使用的是公平鎖(FIFO-First Input First Output)。

5.SynchronousQueue:無緩衝等待隊列,用在【CacheThreadPool】內部,沒有容量,由內部類【SNode]】對象存儲元素,必須等隊列中的添加元素被消費者消費後才能繼續添加新的元素。

五、關於拒絕策略

1.ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出【RejectExecutionException】異常(默認處理方式)
2.ThreadPoolExecutor.DiscardPolicy:丟棄任務,不拋出以常。
3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)。
4.ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務,即由主線程處理任務。

六、如何合理的分配線程數量

此內容來自一個聲音親切熱心可愛的小姐姐,十分感謝
1.CPU密集型:一般指計算比較頻繁的業務情況,【線程數量 = CPU核數 + 1】以減少線程上下文切換。
2.IO密集型:一般指查詢操作比較頻繁的業務情況,分兩種,任務線程並不是一直都在執行任務,應儘可能多的配置線程數量,如【CPU核數 * 2 】,另一種,大部分線程均爲阻塞狀態,需要配置多一點的線程數,參考公式【CPU核數 / (1-阻塞係數)】,阻塞係數一般在【0.8~0.9】之間。

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