Java中五種線程池的介紹

Java中五種線程池的介紹
在Java源碼中,構造方法如圖:
/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param 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.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
簡單介紹下參數含義:
第一個參數corePoolSize:線程池中所保存的線程數,核心線程,包括空閒線程,在初創建線程池時線程不會立即啓動,直到有任務提交纔開始啓動線程並逐漸時線程數目達到corePoolSize。若想一開始就創建所有核心線程需調用prestartAllCoreThreads方法。

第二個參數maximumPoolSize:線程池中允許的最大線程數,需要注意的是當核心線程滿且阻塞隊列也滿時纔會判斷當前線程數是否小於最大線程數,並決定是否創建新線程。 

第三個參數keepAliveTime當線程數大於核心時,多於的空閒線程最多存活時間 。

第四個參數unit:參數的時間單位。

第五個參數workQueue當線程數目超過核心線程數時用於保存任務的隊列。主要有3種類型的BlockingQueue可供選擇:無界隊列,有界隊列和同步移交。

首先看一下新任務進入時線程池的執行策略: 
如果運行的線程少於corePoolSize,則 Executor始終首選添加新的線程,而不進行排隊。(如果當前運行的線程小於corePoolSize,則任務根本不會存入queue中,而是直接運行) 
如果運行的線程大於等於 corePoolSize,則 Executor始終首選將請求加入隊列,而不添加新的線程。 
如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。 

主要有3種類型的BlockingQueue:
1.無界隊列
隊列大小無限制,常用的爲無界的LinkedBlockingQueue,使用該隊列做爲阻塞隊列時要尤其當心,當任務耗時較長時可能會導致大量新任務在隊列中堆積最終導致OOM。最近工作中就遇到因爲採用LinkedBlockingQueue作爲阻塞隊列,部分任務耗時80s+且不停有新任務進來,導致cpu和內存飆升服務器掛掉。

2.有界隊列
常用的有兩類,一類是遵循FIFO原則的隊列如ArrayBlockingQueue與有界的LinkedBlockingQueue,另一類是優先級隊列如PriorityBlockingQueue。PriorityBlockingQueue中的優先級由任務的Comparator決定。 
使用有界隊列時隊列大小需和線程池大小互相配合,線程池較小有界隊列較大時可減少內存消耗,降低cpu使用率和上下文切換,但是可能會限制系統吞吐量。

3.同步移交

如果不希望任務在隊列中等待而是希望將任務直接移交給工作線程,可使用SynchronousQueue作爲等待隊列。SynchronousQueue不是一個真正的隊列,而是一種線程之間移交的機制。要將一個元素放入SynchronousQueue中,必須有另一個線程正在等待接收這個元素。只有在使用無界線程池或者有飽和策略時才建議使用該隊列。

創建線程池的5種方式(jdk1.8之後加了一種)

public class Test {

	public static void main(String[] args) {

		//1.創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			int index = i;
			try {
				Thread.sleep(i * 500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			cachedThreadPool.execute(new Runnable() {

				@Override
				public void run() {
					System.out.println("index: " + index);

				}
			});
		}

		
		//2.創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
		for (int i = 0; i < 10; i++) {
			int index = i;
			fixedThreadPool.execute(new Runnable() {

				@Override
				public void run() {
					try {
						Thread.sleep(1000);
						System.out.println("index: " + index);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			});

		}

		
		//3.創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行
		ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10; i++) {
			final int index = i;
			singleThreadPool.execute(new Runnable() {

				@Override
				public void run() {
					try {
						System.out.println(index);
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

				}
			});
		}

		
		//4.創建一個定長線程池,支持定時及週期性任務執行
		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(0);

		// 第二個參數代表:初始延時多久
		// 第三個參數代表:週期的間隔
		// 第四個參數代表:時間的單位
		scheduledThreadPool.scheduleAtFixedRate(new Runnable() {

			@Override
			public void run() {
				System.out.println("delay 1 seconds, and excute every 3 seconds");
			}
		}, 1, 3, TimeUnit.SECONDS);

		scheduledThreadPool.shutdown();

		//5.newWorkStealingPool創建一個擁有多個任務隊列(以便減少連接數)的線程池
		//1.8的新特性
		ExecutorService newWorkStealingPool = Executors.newWorkStealingPool();

		List<Callable<String>> callables = Arrays.asList(
				callable("task1", 1),
				callable("task2", 2),
				callable("task3", 3));

		try {
			//單個執行線程
			String result = newWorkStealingPool.invokeAny(callables);
			System.out.println(result);
			
			//執行全部
			List<Future<String>> all = newWorkStealingPool.invokeAll(callables);
			//future是一個接口,有幾個方法:boolean cancel(boolean mayInterruptIfRunning);
			//boolean isCancelled();
			//boolean isDone();
			//V get() throws InterruptedException, ExecutionException;
			//V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
			for (Future<String> future : all) {
				String f = future.get();
				System.out.println(f);
			}

		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	static Callable<String> callable(String result, long sleepSeconds) {
	    return () -> {
	        TimeUnit.SECONDS.sleep(sleepSeconds);
	        return result;
	    };
	}


}




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