我們都知道,在JDK1.5之前,java中要進行業務并發時,通常需要有程序員獨立完成代碼實現,當然也有一些開源的框架提供了這些功能,但是這些依然沒有JDK自帶的功能使用起來方便。而當針對高質量Java多線程并發程序設計時,為防止死蹦等現象的出現,比如使用java之前的wait()、notify()和synchronized等,每每需要考慮性能、死鎖、公平性、資源管理以及如何避免線程安全性方面帶來的危害等諸多因素,往往會采用一些較為復雜的安全策略,加重了程序員的開發負擔.萬幸的是,在JDK1.5出現之后,Sun大神(Doug Lea)終于為我們這些可憐的小程序員推出了java.util.concurrent工具包以簡化并發完成。開發者們借助于此,將有效的減少競爭條件(race conditions)和死鎖線程。concurrent包很好的解決了這些問題,為我們提供了更實用的并發程序模型。
Executor :具體Runnable任務的執行者。 ExecutorService :一個線程池管理者,其實現類有多種。我們能把Runnable,Callable提交到池中讓其調度。 Semaphore :一個計數信號量 ReentrantLock :一個可重入的互斥鎖定 Lock,功能類似synchronized,但要強大的多。 Future :是與Runnable,Callable進行交互的接口,比如一個線程執行結束后取返回的結果等等,還提供了cancel終止線程。 BlockingQueue :阻塞隊列。 CompletionService : ExecutorService的擴展,可以獲得線程執行結果的 CountDownLatch :一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。 CyclicBarrier :一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 Future :Future 表示異步計算的結果。 ScheduledExecutorService :一個 ExecutorService,可安排在給定的延遲后運行或定期執行的命令。
newFixedThreadPool(固定大小線程池):創建一個可重用固定線程集合的線程池,以共享的無界隊列方式來運行這些線程(只有要請求的過來,就會在一個隊列里等待執行)。如果在關閉前的執行期間由于失敗而導致任何線程終止,那么一個新線程將代替它執行后續的任務(如果需要)。
newCachedThreadPool(無界線程池,可以進行自動線程回收):創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們。對于執行很多短期異步任務的程序而言,這些線程池通常可提高程序性能。調用 execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。因此,長時間保持空閑的線程池不會使用任何資源。注意,可以使用 ThreadPoolExecutor 構造方法創建具有類似屬性但細節不同(例如超時參數)的線程池。
newSingleThreadExecutor(單個后臺線程):創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。(注意,如果因為在關閉前的執行期間出現失敗而終止了此單個線程,那么如果需要,一個新線程將代替它執行后續的任務)。可保證順序地執行各個任務,并且在任意給定的時間不會有多個線程是活動的。與其他等效的 newFixedThreadPool(1) 不同,可保證無需重新配置此方法所返回的執行程序即可使用其他的線程。
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(4); for (int i = 0 ; i < 5;i++){ service.execute(new WorkerExecutor(i)); } }}class WorkerExecutor extends Thread{ PRivate int mIndex; public WorkerExecutor(int index){ this.mIndex = index; } @Override public void run() { super.run(); try { System.out.println("Worker:["+this.mIndex+"]--->start...."); Thread.sleep((int)(Math.random()*10000)); System.out.println("Worker:["+this.mIndex+"]--->end...."); } catch (InterruptedException e) { e.printStackTrace(); } }}打印如下:
Worker:[1]--->start....Worker:[0]--->start....Worker:[2]--->start....Worker:[3]--->start....Worker:[1]--->end....Worker:[4]--->start....Worker:[0]--->end....Worker:[2]--->end....Worker:[4]--->end....Worker:[3]--->end....上面代碼只能執行4個線程,如果4個線程都有任務那么之后的只能進入隊列等待也就是說,我們將所有的線程提交后,線程池會等待執行完最后shutdown。我們也會發現,提交的線程被放到一個“無界隊列里”。這是一個有序隊列(BlockingQueue,這個下面會說到)。另外它使用了Executors的靜態函數生成一個固定的線程池,顧名思義,線程池的線程是不會釋放的,即使它是Idle。這就會產生性能問題,比如如果線程池的大小為200,當全部使用完畢后,所有的線程會繼續留在池中,相應的內存和線程切換(while(true)+sleep循環)都會增加。如果要避免這個問題,就必須直接使用ThreadPoolExecutor()來構造。可以像通用的線程池一樣設置“最大線程數”、“最小線程數”和“空閑線程keepAlive的時間”。
int corePoolSize = 4; int maximumPoolSize = 10; long keepAliveTime = 60; ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(maximumPoolSize); ExecutorService service = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, blockingQueue); for (int i = 0; i < 4; i++) { service.execute(new WorkerExecutor(i)); }(ThreadPoolExecutor 詳細介紹:http://blog.csdn.net/wangwenhui11/article/details/6760474)
支持兩個附加操作的 Queue,這兩個操作是:檢索元素時等待隊列變為非空,以及存儲元素時等待空間變得可用。BlockingQueue 不接受 null 元素。試圖 add、put 或 offer 一個 null 元素時,某些實現會拋出 NullPointerException。null 被用作指示 poll 操作失敗的警戒值。BlockingQueue 可以是限定容量的。它在任意給定時間都可以有一個 remainingCapacity,超出此容量,便無法無阻塞地 put 額外的元素。沒有任何內部容量約束的 BlockingQueue 總是報告 Integer.MAX_VALUE 的剩余容量。BlockingQueue 實現主要用于生產者-使用者隊列,但它另外還支持 Collection 接口。因此,舉例來說,使用 remove(x) 從隊列中移除任意一個元素是有可能的。然而,這種操作通常不會有效執行,只能有計劃地偶爾使用,比如在取消排隊信息時。BlockingQueue 實現是線程安全的。所有排隊方法都可以使用內部鎖定或其他形式的并發控制來自動達到它們的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)沒有必要自動執行,除非在實現中特別說明。因此,舉例來說,在只添加了c中的一些元素后,addAll(c) 有可能失敗(拋出一個異常)。BlockingQueue 實質上不支持使用任何一種“close”或“shutdown”操作來指示不再添加任何項。這種功能的需求和使用有依賴于實現的傾向。例如,一種常用的策略是:對于生產者,插入特殊的 end-of-stream 或 poison 對象,并根據使用者獲取這些對象的時間來對它們進行解釋。
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { service.submit(new WorkerExecutor(i)); } Thread thread = new Thread() { @Override public void run() { super.run(); try { while (true) { Thread.sleep((int) (Math.random() * 1000)); if (WorkerExecutor.mBlockingQueue.isEmpty()) break; String str = WorkerExecutor.mBlockingQueue.take(); System.out.println(str + " has take!"); } } catch (InterruptedException e) { e.printStackTrace(); } } }; service.submit(thread); service.shutdown(); }}class WorkerExecutor extends Thread { public static BlockingQueue<String> mBlockingQueue = new LinkedBlockingQueue<>(3); private int mIndex; public WorkerExecutor(int index) { this.mIndex = index; } @Override public void run() { super.run(); try { mBlockingQueue.put(String.valueOf(mIndex)); System.out.println("{" + this.mIndex + "} in queue!"); } catch (InterruptedException e) { e.printStackTrace(); } }}執行結果如下:
{1} in queue!{2} in queue!{0} in queue!0 has take!{3} in queue!1 has take!{4} in queue!2 has take!{5} in queue!3 has take!{6} in queue!4 has take!{7} in queue!5 has take!{8} in queue!6 has take!{9} in queue!7 has take!8 has take!9 has take!Semaphore是一個計數信號量,它的本質是一個”共享鎖”。 信號量維護了一個信號量許可集。線程可以通過調用acquire()來獲取信號量的許可;當信號量中有可用的許可時,線程能獲取該許可;否則線程必須等待,直到有可用的許可為止。 線程可以通過release()來釋放它所持有的信號量許可 Semaphore 通常用于限制可以訪問某些資源(物理或邏輯的)的線程數目。例如,下面的類使用信號量控制對內容池的訪問:
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); Semaphore semaphore = new Semaphore(2); for (int i = 0; i < 10; i++) { service.submit(new WorkerExecutor(i+1,semaphore)); } service.shutdown(); semaphore.acquireUninterruptibly(2);//從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞。 System.out.println("全部使用完畢"); semaphore.release(); }}class WorkerExecutor extends Thread { private Semaphore position; private int id; public WorkerExecutor(int id, Semaphore pos) { this.id = id; this.position = pos; } @Override public void run() { super.run(); try { if (position.availablePermits() > 0) {//返回此信號量中當前可用的許可數。 System.out.println("worker"+this.id + ": 有可用許可"); } else { System.out.println("worker"+this.id + ": 沒有可用許可 等待"); } position.acquire();// 從此信號量獲取一個許可,在提供一個許可前一直將線程阻塞,否則線程被中斷。 System.out.println("worker"+this.id+":獲得許可"); Thread.sleep((int)(Math.random()*1000)); System.out.println("worker"+this.id+":使用完畢 釋放許可"); position.release();//釋放一個許可,將其返回給信號量 } catch (Exception e) { e.printStackTrace(); } }}運行結果如下:
worker1: 有可用許可worker1:獲得許可worker2: 有可用許可worker2:獲得許可worker3: 沒有可用許可 等待worker4: 沒有可用許可 等待worker5: 沒有可用許可 等待worker6: 沒有可用許可 等待worker7: 沒有可用許可 等待worker8: 沒有可用許可 等待worker9: 沒有可用許可 等待worker10: 沒有可用許可 等待worker2:使用完畢 釋放許可worker3:獲得許可worker1:使用完畢 釋放許可worker4:獲得許可worker3:使用完畢 釋放許可worker5:獲得許可worker4:使用完畢 釋放許可worker6:獲得許可worker6:使用完畢 釋放許可worker7:獲得許可worker5:使用完畢 釋放許可worker8:獲得許可worker7:使用完畢 釋放許可worker9:獲得許可worker9:使用完畢 釋放許可worker10:獲得許可worker8:使用完畢 釋放許可worker10:使用完畢 釋放許可全部使用完畢ReentrantLock可以等同于synchronized使用、但是它比synchronized有更強的功能、可以提供更靈活的鎖機制、同時減少死鎖的發生概率。我們平時用用synchronized也就夠了、但是要寫好一個復雜的多線程系統、為了提供更靈活的同步機制、就需要用到ReentrantLock了。 ReentrantLock 將由最近成功獲得鎖定,并且還沒有釋放該鎖定的線程所擁有。當鎖定沒有被另一個線程所擁有時,調用 lock 的線程將成功獲取該鎖定并返回。如果當前線程已經擁有該鎖定,此方法將立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發生。 此類的構造方法接受一個可選的公平參數。當設置為true時,在多個線程的爭用下,這些鎖定傾向于將訪問權授予等待時間最長的線程。否則此鎖定將無法保證任何特定訪問順序。與采用默認設置(使用不公平鎖定)相比,使用公平鎖定的程序在許多線程訪問時表現為很低的總體吞吐量(即速度很慢,常常極其慢),但是在獲得鎖定和保證鎖定分配的均衡性時差異較小。不過要注意的是,公平鎖定不能保證線程調度的公平性。因此,使用公平鎖定的眾多線程中的一員可能獲得多倍的成功機會,這種情況發生在其他活動線程沒有被處理并且目前并未持有鎖定時。還要注意的是,未定時的 tryLock 方法并沒有使用公平設置。因為即使其他線程正在等待,只要該鎖定是可用的,此方法就可以獲得成功。
以上幾個特點使ReentrantLock相比synchronized的鎖粒度更細、使用起來更靈活、可以滿足更多的功能。 示例如下
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); TestReentrantLock lock = new TestReentrantLock(); for (int i = 0; i < 10; i++) { service.submit(new WorkerExecutor(i + 1, lock)); } service.shutdown(); }}class WorkerExecutor extends Thread { private TestReentrantLock mLock; private int id; public WorkerExecutor(int id, TestReentrantLock lock) { this.id = id; this.mLock = lock; } @Override public void run() { super.run(); mLock.print(id); }}class TestReentrantLock { private ReentrantLock mLock = new ReentrantLock(); public void print(int id) { try { mLock.lock(); System.out.println(id + "獲得"); Thread.sleep((int) (Math.random() * 1000)); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(id + "釋放"); mLock.unlock(); } }}當向Executor提交批處理任務時,并且希望在它們完成后獲得結果,如果用FutureTask,你可以循環獲取task,并用future.get()去獲取結果,但是如果這個task沒有完成,你就得阻塞在這里,這個實效性不高,其實在很多場合,其實你拿第一個任務結果時,此時結果并沒有生成并阻塞,其實在阻塞在第一個任務時,第二個task的任務已經早就完成了,顯然這種情況用future task不合適的,效率也不高。
自己維護list和CompletionService的區別:
從list中遍歷的每個Future對象并不一定處于完成狀態,這時調用get()方法就會被阻塞住,如果系統是設計成每個線程完成后就能根據其結果繼續做后面的事,這樣對于處于list后面的但是先完成的線程就會增加了額外的等待時間。CompletionService采取的是BlockingQueue無界隊列來管理Future。則有一個線程執行完畢把返回結果放到BlockingQueue里面。就可以通過completionServcie.take().get()取出結果。方法區別:
take 方獲取并移除表示下一個已完成任務的 Future,如果目前不存在這樣的任務,則等待。<如果需要用到返回值建議用take>poll 獲取并移除表示下一個已完成任務的 Future,如果不存在這樣的任務,則返回null。以下是jdk關于CompletionService的簡介:
public interface CompletionService將生產新的異步任務與使用已完成任務的結果分離開來的服務。生產者 submit 執行的任務。使用者 take 已完成的任務,并按照完成這些任務的順序處理它們的結果。例如,CompletionService 可以用來管理異步 IO ,執行讀操作的任務作為程序或系統的一部分提交,然后,當完成讀操作時,會在程序的不同部分執行其他操作,執行操作的順序可能與所請求的順序不同。通常,CompletionService 依賴于一個單獨的 Executor 來實際執行任務,在這種情況下,CompletionService 只管理一個內部完成隊列。ExecutorCompletionService 類提供了此方法的一個實現。 內存一致性效果:線程中向 CompletionService 提交任務之前的操作 happen-before 該任務執行的操作,后者依次 happen-before 緊跟在從對應 take() 成功返回的操作。 public class Test { public static class Task implements Callable<Integer> { private int i; public Task(int i) { this.i = i; } @Override public Integer call() throws Exception { Thread.sleep(new Random().nextInt(5000)); System.out.println(Thread.currentThread().getName() + " " + i); return i; } } public void run() { ExecutorService pool = Executors.newFixedThreadPool(10); CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(pool); try { for (int i = 0; i < 10; i++) completionService.submit(new Task(i)); for (int i = 0; i < 10; i++) System.out.println(completionService.take().get()); } catch (Exception e) { e.printStackTrace(); } finally { pool.shutdown(); } } public static void main(String[] args) throws Exception { new Test().run(); }}運行結果
pool-1-thread-6 55pool-1-thread-7 66pool-1-thread-3 22pool-1-thread-8 77pool-1-thread-1 00pool-1-thread-2 11pool-1-thread-9 88pool-1-thread-5 44pool-1-thread-10 99pool-1-thread-4 33從結果中不難看出。只要有一個線程執行完畢后,主程序就立馬獲取結果。
一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。用給定的計數 初始化 CountDownLatch。由于調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之后,會釋放所有等待的線程,await 的所有后續調用都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。CountDownLatch 的一個有用特性是,它不要求調用 countDown 方法的線程等到計數到達零時才繼續,而在所有線程都能通過之前,它只是阻止任何線程繼續通過一個 await。
public class Test { public static void main(String[] args) throws Exception { VideoConference conference = new VideoConference(10); Thread threadConderence = new Thread(conference); threadConderence.start(); for (int i = 0; i < 10; i++) { Participant participant = new Participant(conference, "Participant " + i); Thread thread = new Thread(participant); thread.start(); } }}class VideoConference implements Runnable { private final CountDownLatch mCountDownLatch; public VideoConference(int number) { mCountDownLatch = new CountDownLatch(number); } public void arrive(String name) { System.out.println(name + " has arrived."); mCountDownLatch.countDown(); System.out.println("VideoConference:Waiting for " + mCountDownLatch.getCount()); } @Override public void run() { System.out.println("VideoConference:Initialization:" + mCountDownLatch.getCount()); try { mCountDownLatch.await(); System.out.printf("VideoConference: All the participants have come/n"); System.out.printf("VideoConference: Let's start.../n"); } catch (InterruptedException e) { e.printStackTrace(); } }}class Participant implements Runnable { private VideoConference mVideoConference; private String name; public Participant(VideoConference conference, String name) { this.mVideoConference = conference; this.name = name; } @Override public void run() { long duration = (long) (Math.random() * 10); try { TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } mVideoConference.arrive(name); }}運行結果如下
VideoConference:Initialization:10Participant 0 has arrived.VideoConference:Waiting for 9Participant 9 has arrived.VideoConference:Waiting for 8Participant 3 has arrived.Participant 8 has arrived.VideoConference:Waiting for 7VideoConference:Waiting for 6Participant 7 has arrived.VideoConference:Waiting for 5Participant 4 has arrived.VideoConference:Waiting for 4Participant 5 has arrived.VideoConference:Waiting for 3Participant 2 has arrived.VideoConference:Waiting for 2Participant 6 has arrived.VideoConference:Waiting for 1Participant 1 has arrived.VideoConference:Waiting for 0VideoConference: All the participants have comeVideoConference: Let's start...CountDownLatch最重要的方法是countDown()和await(),前者主要是倒數一次,后者是等待倒數到0,如果沒有到達0,就只有阻塞等待了。
一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的線程的程序中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrier 在釋放等待線程后可以重用,所以稱它為循環 的 barrier。CyclicBarrier 支持一個可選的 Runnable 命令,在一組線程中的最后一個線程到達之后(但在釋放所有線程之前),該命令只在每個屏障點運行一次。若在繼續所有參與線程之前更新共享狀態,此屏障操作 很有用。 示例用法:下面是一個在并行分解設計中使用 barrier 的例子,很經典的旅行團例子:
public class Test { // 徒步需要的時間: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan private static int[] timeWalk = {5, 8, 15, 15, 10}; // 自駕游 private static int[] timeSelf = {1, 3, 4, 4, 5}; // 旅游大巴 private static int[] timeBus = {2, 4, 6, 6, 7}; static String now() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); return sdf.format(new Date()) + ": "; } static class Tour implements Runnable { private int[] times; private CyclicBarrier barrier; private String tourName; public Tour(CyclicBarrier barrier, String tourName, int[] times) { this.times = times; this.tourName = tourName; this.barrier = barrier; } @Override public void run() { try { Thread.sleep(times[0] * 1000); System.out.println(now() + tourName + " Reached Shenzhen"); barrier.await(); Thread.sleep(times[1] * 1000); System.out.println(now() + tourName + " Reached Guangzhou"); barrier.await(); Thread.sleep(times[2] * 1000); System.out.println(now() + tourName + " Reached Shaoguan"); barrier.await(); Thread.sleep(times[3] * 1000); System.out.println(now() + tourName + " Reached Changsha"); barrier.await(); Thread.sleep(times[4] * 1000); System.out.println(now() + tourName + " Reached Wuhan"); barrier.await(); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { // 三個旅行團 CyclicBarrier barrier = new CyclicBarrier(3); ExecutorService exec = Executors.newFixedThreadPool(3); exec.submit(new Tour(barrier, "WalkTour", timeWalk)); exec.submit(new Tour(barrier, "SelfTour", timeSelf)); //當我們把下面的這段代碼注釋后,會發現,程序阻塞了,無法繼續運行下去。 exec.submit(new Tour(barrier, "BusTour", timeBus)); exec.shutdown(); }}運行結果如下:
15:29:38: SelfTour Reached Shenzhen15:29:38: BusTour Reached Shenzhen15:29:41: WalkTour Reached Shenzhen15:29:44: SelfTour Reached Guangzhou15:29:45: BusTour Reached Guangzhou15:29:49: WalkTour Reached Guangzhou15:29:53: SelfTour Reached Shaoguan15:29:55: BusTour Reached Shaoguan15:30:04: WalkTour Reached Shaoguan15:30:08: SelfTour Reached Changsha15:30:10: BusTour Reached Changsha15:30:19: WalkTour Reached Changsha15:30:24: SelfTour Reached Wuhan15:30:26: BusTour Reached Wuhan15:30:29: WalkTour Reached WuhanCyclicBarrier最重要的屬性就是參與者個數,另外最重要方法是await()。當所有線程都調用了await()后,就表示這些線程都可以繼續執行,否則就會等待。
Future 表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果。計算完成后只能使用 get 方法來檢索結果,如有必要,計算完成前可以阻塞此方法。取消則由 cancel 方法來執行。還提供了其他方法,以確定任務是正常完成還是被取消了。一旦計算完成,就不能再取消計算。如果為了可取消性而使用 Future但又不提供可用的結果,則可以聲明 Future
一個 ExecutorService,可安排在給定的延遲后運行或定期執行的命令。schedule 方法使用各種延遲創建任務,并返回一個可用于取消或檢查執行的任務對象。scheduleAtFixedRate 和 scheduleWithFixedDelay 方法創建并執行某些在取消前一直定期運行的任務。用Executor.execute(java.lang.Runnable) 和 ExecutorService 的 submit 方法所提交的命令,通過所請求的 0 延遲進行安排。schedule 方法中允許出現 0 和負數延遲(但不是周期),并將這些視為一種立即執行的請求。所有的 schedule 方法都接受相對 延遲和周期作為參數,而不是絕對的時間或日期。將以 Date 所表示的絕對時間轉換成要求的形式很容易。例如,要安排在某個時刻以后的日期運行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。但是要注意,由于網絡時間同步協議、時鐘漂移或其他因素的存在,因此相對延遲的期滿日期不必與啟用任務的當前 Date 相符。Executors 類為此包中所提供的 ScheduledExecutorService 實現提供了便捷的工廠方法。
public class Test { public static void main(String[] args) { final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); final Runnable beeper = new Runnable() { int count = 0; @Override public void run() { System.out.println(new Date() + " beep " + (++count)); } }; // 1秒鐘后運行,并每隔2秒運行一次 final ScheduledFuture beeperHandle = scheduler.scheduleWithFixedDelay(beeper, 2, 5, TimeUnit.SECONDS); // 2秒鐘后運行,并每次在上次任務運行完后等待5秒后重新運行 final ScheduledFuture beeperHandle2 = scheduler.scheduleWithFixedDelay(beeper, 2, 5, TimeUnit.SECONDS); // 30秒后結束關閉任務,并且關閉Scheduler scheduler.schedule(new Runnable() { @Override public void run() { beeperHandle.cancel(true); beeperHandle2.cancel(true); scheduler.shutdown(); } }, 30, TimeUnit.SECONDS); }}運行結果:
Sat Feb 04 16:02:20 CST 2017 beep 2Sat Feb 04 16:02:20 CST 2017 beep 1Sat Feb 04 16:02:25 CST 2017 beep 3Sat Feb 04 16:02:25 CST 2017 beep 4Sat Feb 04 16:02:30 CST 2017 beep 5Sat Feb 04 16:02:30 CST 2017 beep 6Sat Feb 04 16:02:35 CST 2017 beep 7Sat Feb 04 16:02:35 CST 2017 beep 8Sat Feb 04 16:02:40 CST 2017 beep 9Sat Feb 04 16:02:40 CST 2017 beep 10Sat Feb 04 16:02:45 CST 2017 beep 11Sat Feb 04 16:02:45 CST 2017 beep 12以上轉自: (http://blog.csdn.net/wulei_longhe/article/details/30032031)(blog.csdn.net/tsyj810883979/article/details/6956290) (http://hyxw5890.VEvb.com/blog/1578597) (https://my.oschina.net/jielucky/blog/158839)
新聞熱點
疑難解答