Java開發筆記(一百零五)幾種定時器線程池

前面介紹了普通線程池的用法,就大多數任務而言,它們對具體的執行時機並沒有特殊要求,最可能是但願早點跑完早點出結果。不過對於須要定時執行的任務來講,它們要求在特定的時間點運行,而且每每不止運行一次,還要週期性地反覆運行。因爲普通線程池知足不了此類定時運行的需求,所以Java又提供了定時器線程池來實現定時與週期執行任務的功能。
普通線程池的工具類名叫ExecutorService,定時器線程池的工具類則叫作ScheduledExecutorService,添加了Scheduled前綴,表示它是一種有計劃的、預先安排好的線程池。有別於劃分了四大類的普通線程池,定時器線程池僅僅分紅了兩類:單線程的定時器線程池和固定數量的定時器線程池。其中單線程的定時器線程池經過newSingleThreadScheduledExecutor方法得到,它的建立代碼示例以下:html

		// 建立一個延遲一次的單線程定時器
		ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor();

 

至於固定數量的定時器線程池則經過newScheduledThreadPool方法得到,它的建立代碼示例以下:多線程

		// 建立一個延遲一次的多線程定時器(線程池大小爲3)
		ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);

 

雖然定時器線程池只有兩類,但定時器的調度方式有三種之多,主要是依據啓動次數與週期長度來劃分,詳細說明以下:
一、定時任務只啓動一次。
此時調用線程池對象的schedule方法,該方法的第一個參數爲任務實例,第二個和第三個參數分別是延遲執行的時長及其單位。
二、每間隔若干時間週期啓動定時任務。
此時調用線程池對象的scheduleAtFixedRate方法,該方法的第一個參數爲任務實例,第二個參數爲首次執行的延遲時長,第三個參數分別爲後續運行的間隔時長,第四個參數則爲時長單位。
三、固定延遲若干時間啓動定時任務。
此時調用線程池對象的scheduleWithFixedDelay方法,該方法的參數說明基本同scheduleAtFixedRate方法。兩個方法的區別在於:前者的間隔時間從上個任務的開始時間起計算,後者的間隔時間從上個任務的結束時間起計算。
除了以上的三個調度方法,ScheduledExecutorService還擁有ExecutorService的所有方法,包括getPoolSize、getActiveCount、shutdown等等,由於它原本就是從ExecutorService派生而來的呀。ide

下面作個實驗觀察一下兩種定時器線程池的運行過程,實驗開始前先定義一個參觀任務,主要用來打印當前的操做日誌,包括操做時間、操做線程、操做描述等信息。參觀任務的代碼例子以下所示:工具

	// 定義一個參觀任務
	private static class Visit implements Runnable {
		private String name; // 任務名稱
		private int index; // 任務序號
		public Visit(String name, int index) {
			this.name = name;
			this.index = index;
		}
		
		@Override
		public void run() {
			// 如下打印操做日誌,包括操做時間、操做線程、操做描述等信息
			String desc = String.format("%s的第%d個任務到此一遊", name, index);
			PrintUtils.print(Thread.currentThread().getName(), desc);
		}
	};

 

而後命令單線程的定時器線程池調用schedule方法執行一次的定時任務,具體的實驗代碼示例以下:測試

	// 測試延遲一次的單線程定時器
	private static void testSingleScheduleOnce() {
		// 建立一個延遲一次的單線程定時器
		ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor();
		for (int i=0; i<5; i++) { // 循環開展5個調度
			// 建立一個參觀任務
			Visit visit = new Visit("延遲一次的單線程定時器", i);
			// 命令線程池開展任務調度。延遲1秒後執行參觀任務
			pool.schedule(visit, 1, TimeUnit.SECONDS);
		}
	}

 

運行以上的實驗代碼,觀察到以下的線程池日誌:this

15:49:16.122 pool-1-thread-1 延遲一次的單線程定時器的第0個任務到此一遊
15:49:16.123 pool-1-thread-1 延遲一次的單線程定時器的第1個任務到此一遊
15:49:16.123 pool-1-thread-1 延遲一次的單線程定時器的第2個任務到此一遊
15:49:16.124 pool-1-thread-1 延遲一次的單線程定時器的第3個任務到此一遊
15:49:16.124 pool-1-thread-1 延遲一次的單線程定時器的第4個任務到此一遊

 

由日誌可見,該定時器線程池自始至終只有惟一一個的線程在運行。線程

再來測試固定數量的定時器線程池,此時換成調用scheduleAtFixedRate方法,準備以固定頻率週期性地執行定時任務,具體的實驗代碼示例以下:日誌

	// 測試固定速率的多線程定時器
	private static void testMultiScheduleRate() {
		// 建立一個固定速率的多線程定時器(線程池大小爲3)
		ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
		for (int i=0; i<5; i++) { // 循環開展5個調度
			// 建立一個參觀任務
			Visit visit = new Visit("固定速率的多線程定時器", i);
			// 命令線程池開展任務調度。第一次延遲1秒後執行參觀任務,之後每間隔3秒執行下一個參觀任務
			pool.scheduleAtFixedRate(visit, 1, 3, TimeUnit.SECONDS);
		}
	}

 

運行以上的實驗代碼,觀察到以下的線程池日誌:orm

15:50:21.859 pool-1-thread-1 固定速率的多線程定時器的第0個任務到此一遊
15:50:21.859 pool-1-thread-2 固定速率的多線程定時器的第1個任務到此一遊
15:50:21.859 pool-1-thread-3 固定速率的多線程定時器的第2個任務到此一遊
15:50:21.860 pool-1-thread-3 固定速率的多線程定時器的第3個任務到此一遊
15:50:21.861 pool-1-thread-3 固定速率的多線程定時器的第4個任務到此一遊
15:50:24.790 pool-1-thread-3 固定速率的多線程定時器的第1個任務到此一遊
15:50:24.791 pool-1-thread-3 固定速率的多線程定時器的第3個任務到此一遊
15:50:24.792 pool-1-thread-3 固定速率的多線程定時器的第4個任務到此一遊
15:50:24.793 pool-1-thread-2 固定速率的多線程定時器的第2個任務到此一遊
15:50:24.798 pool-1-thread-1 固定速率的多線程定時器的第0個任務到此一遊

 

由日誌可見,該定時器線程池一共開啓了三個線程來執行定時任務,注意到每一個任務的先後日誌間隔時間不足3秒,正好說明間隔的3秒並不是先後兩次運行的首尾間隔。htm

那麼調用方法改爲scheduleWithFixedDelay,試試以固定間隔週期性地執行定時任務會是什麼樣的,具體的實驗代碼示例以下:

	// 測試固定延遲的多線程定時器
	private static void testMultiScheduleDelay() {
		// 建立一個固定速率的多線程定時器(線程池大小爲3)
		ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
		for (int i=0; i<5; i++) { // 循環開展5個調度
			// 建立一個參觀任務
			Visit visit = new Visit("固定延遲的多線程定時器", i);
			// 命令線程池開展任務調度。第一次延遲1秒後執行參觀任務,之後每3秒執行下一個參觀任務
			pool.scheduleWithFixedDelay(visit, 1, 3, TimeUnit.SECONDS);
		}
	}

 

運行以上的實驗代碼,觀察到以下的線程池日誌:

16:10:19.281 pool-1-thread-1 固定延遲的多線程定時器的第0個任務到此一遊
16:10:19.281 pool-1-thread-2 固定延遲的多線程定時器的第1個任務到此一遊
16:10:19.281 pool-1-thread-3 固定延遲的多線程定時器的第2個任務到此一遊
16:10:19.283 pool-1-thread-3 固定延遲的多線程定時器的第3個任務到此一遊
16:10:19.283 pool-1-thread-2 固定延遲的多線程定時器的第4個任務到此一遊
16:10:22.283 pool-1-thread-1 固定延遲的多線程定時器的第1個任務到此一遊
16:10:22.284 pool-1-thread-2 固定延遲的多線程定時器的第3個任務到此一遊
16:10:22.285 pool-1-thread-3 固定延遲的多線程定時器的第2個任務到此一遊
16:10:22.286 pool-1-thread-3 固定延遲的多線程定時器的第4個任務到此一遊
16:10:22.287 pool-1-thread-1 固定延遲的多線程定時器的第0個任務到此一遊

 

由日誌可見,此時每一個任務的先後日誌時間均不小於3秒,證實了scheduleWithFixedDelay方法的確採起了固定間隔而非固定速率。



更多Java技術文章參見《Java開發筆記(序)章節目錄

相關文章
相關標籤/搜索