java並發6-小結

來源:互聯網
上載者:User

標籤:

為什麼需要並發

  並發其實是一種解耦合的策略,它協助我們把做什麼(目標)和什麼時候做(時機)分開。這樣做可以明顯改進應用程式的輸送量(獲得更多的CPU調度時間)和結構(程式有多個部分在協同工作)。做過Java Web開發的人都知道,Java Web中的Servlet程式在Servlet容器的支援下採用單一實例多線程的工作模式,Servlet容器為你處理了並發問題。

誤解和正解

  最常見的對並發編程的誤解有以下這些:

-並發總能改進效能(並發在CPU有很多空閑時間時能明顯改進程式的效能,但當線程數量較多的時候,線程間頻繁的調度切換反而會讓系統的效能下降) 
-編寫並發程式無需修改原有的設計(目的與時機的解耦往往會對系統結構產生巨大的影響) 
-在使用Web或EJB容器時不用關注並發問題(只有瞭解了容器在做什麼,才能更好的使用容器)

  下面的這些說法才是對並發客觀的認識:

-編寫並發程式會在代碼上增加額外的開銷 
-正確的並發是非常複雜的,即使對於很簡單的問題 
-並發中的缺陷因為不易重現也不容易被發現 
-並發往往需要對設計策略從根本上進行修改

並發編程的原則和技巧單一職責原則

分離並發相關代碼和其他代碼(並發相關代碼有自己的開發、修改和調優生命週期)。

限制資料範圍

兩個線程修改共用對象的同一欄位時可能會相互幹擾,導致不可預期的行為,解決方案之一是構造臨界區,但是必須限制臨界區的數量。

使用資料副本

資料副本是避免共用資料的好方法,複製出來的對象只是以唯讀方式對待。Java 5的java.util.concurrent包中增加一個名為CopyOnWriteArrayList的類,它是List介面的子類型,所以你可以認為它是ArrayList的安全執行緒的版本,它使用了寫時複製的方式建立資料副本進行操作來避免對共用資料並發訪問而引發的問題。

線程應儘可能獨立

讓線程存在於自己的世界中,不與其他線程共用資料。有過Java Web開發經驗的人都知道,Servlet就是以單一實例多線程的方式工作,和每個請求相關的資料都是通過Servlet子類的service方法(或者是doGet或doPost方法)的參數傳入的。只要Servlet中的代碼只使用局部變數,Servlet就不會導致同步問題。Spring MVC的控制器也是這麼做的,從請求中獲得的對象都是以方法的參數傳入而不是作為類的成員,很明顯Struts 2的做法就正好相反,因此Struts 2中作為控制器的Action類都是每個請求對應一個執行個體。

Java 5以前的並發編程

Java的執行緒模式建立在搶佔式線程調度的基礎上,也就是說:

  • 所有線程可以很容易的共用同一進程中的對象。
  • 能夠引用這些對象的任何線程都可以修改這些對象。
  • 為了保護資料,對象可以被鎖住。

  Java基於線程和鎖的並發過於底層,而且使用鎖很多時候都是很萬惡的,因為它相當於讓所有的並發都變成了排隊等待。 
  在Java 5以前,可以用synchronized關鍵字來實現鎖的功能,它可以用在代碼塊和方法上,表示在執行整個代碼塊或方法之前線程必須取得合適的鎖。對於類的非靜態方法(成員方法)而言,這意味這要取得對象執行個體的鎖,對於類的靜態方法(類方法)而言,要取得類的Class對象的鎖,對於同步代碼塊,程式員可以指定要取得的是那個對象的鎖。 
  不管是同步代碼塊還是同步方法,每次只有一個線程可以進入,如果其他線程試圖進入(不管是同一同步塊還是不同的同步塊),JVM會將它們掛起(放入到等鎖池中)。這種結構在並發理論中稱為臨界區(critical section)。這裡我們可以對Java中用synchronized實現同步和鎖的功能做一個總結:

  • 只能鎖定對象,不能鎖定基礎資料型別 (Elementary Data Type)
  • 被鎖定的對象數組中的單個對象不會被鎖定
  • 同步方法可以視為包含整個方法的synchronized(this) { … }代碼塊
  • 靜態同步方法會鎖定它的Class對象
  • 內部類的同步是獨立於外部類的
  • synchronized修飾符並不是方法簽名的組成部分,所以不能出現在介面的方法聲明中
  • 非同步的方法不關心鎖的狀態,它們在同步方法運行時仍然可以得以運行
  • synchronized實現的鎖是可重新進入的鎖。

  在JVM內部,為了提高效率,同時啟動並執行每個線程都會有它正在處理的資料的快取複本,當我們使用synchronzied進行同步的時候,真正被同步的是在不同線程中表示被鎖定對象的記憶體塊(副本資料會保持和主記憶體的同步,現在知道為什麼要用同步這個詞彙了吧),簡單的說就是在同步塊或同步方法執行完後,對被鎖定的對象做的任何修改要在釋放鎖之前寫回到主記憶體中;在進入同步塊得到鎖之後,被鎖定對象的資料是從主記憶體中讀出來的,持有鎖的線程的資料副本一定和主記憶體中的資料檢視是同步的 。 
  在Java最初的版本中,就有一個叫volatile的關鍵字,它是一種簡單的同步的處理機制,因為被volatile修飾的變數遵循以下規則:

  • 變數的值在使用之前總會從主記憶體中再讀取出來。
  • 對變數值的修改總會在完成之後寫回到主記憶體中。

  使用volatile關鍵字可以在多線程環境下預防編譯器不正確的最佳化假設(編譯器可能會將在一個線程中值不會發生改變的變數最佳化成常量),但只有修改時不依賴目前狀態(讀取時的值)的變數才應該聲明為volatile變數。 
  不變模式也是並發編程時可以考慮的一種設計。讓對象的狀態是不變的,如果希望修改對象的狀態,就會建立對象的副本並將改變寫入副本而不改變原來的對象,這樣就不會出現狀態不一致的情況,因此不變對象是安全執行緒的。Java中我們使用頻率極高的String類就採用了這樣的設計。

Java 5的並發編程

Doug Lea在Java 5中提供了他裡程碑式的傑作java.util.concurrent包,它的出現讓Java的並發編程有了更多的選擇和更好的工作方式:

  • Executor :具體Runnable任務的執行者。ExecutorService :一個線程池管理者,其實作類別有多種,我會介紹一部分。我們能把Runnable,Callable提交到池中讓其調度
    Semaphore :一個計數訊號量
    ReentrantLock :一個可重新進入的互斥鎖定 Lock,功能類似synchronized,但要強大的多。
    Future :是與Runnable,Callable進行互動的介面,比如一個線程執行結束後取返回的結果等等,還提供了cancel終止線程。
    BlockingQueue :阻塞隊列。
    CompletionService : ExecutorService的擴充,可以獲得線程執行結果的
    CountDownLatch :一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。
    CyclicBarrier :一個同步輔助類,它允許一組線程互相等待,直到到達某個公用屏障點
    Future :Future 表示非同步計算的結果。
    ScheduledExecutorService :一個 ExecutorService,可安排在給定的延遲後運行或定期執行的命令。
    接下來逐一介紹
    ExecutorsnewFixedThreadPool(固定大小線程池)
    建立一個可重用固定線程集合的線程池,以共用的無界隊列方式來運行這些線程(只有要請求的過來,就會在一個隊列裡等待執行)。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。

    newCachedThreadPool(無界線程池,可以進行自動線程回收)
    建立一個可根據需要建立新線程的線程池,但是在以前構造的線程可用時將重用它們。對於執行很多短期非同步任務的程式而言,這些線程池通常可提高程式效能。調用 execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則建立一個新線程並添加到池中。終止並從緩衝中移除那些已有 60 秒鐘未被使用的線程。因此,長時間保持閒置線程池不會使用任何資源。注意,可以使用 ThreadPoolExecutor 構造方法建立具有類似屬性但細節不同(例如逾時參數)的線程池。

    newSingleThreadExecutor(單個後台線程)
    建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。(注意,如果因為在關閉前的執行期間出現失敗而終止了此單個線程,那麼如果需要,一個新線程將代替它執行後續的任務)。可保證順序地執行各個任務,並且在任意給定的時間不會有多個線程是活動的。與其他等效的 newFixedThreadPool(1) 不同,可保證無需重新設定此方法所返回的執行程式即可使用其他的線程。

    這些方法返回的都是ExecutorService對象,這個對象可以理解為就是一個線程池。也可直接使用ThreadPoolExecutor()來構造。可以像通用的線程池一樣設定“最大線程數”、“最小線程數”和“空閑線程keepAlive的時間”。Semaphore一個計數訊號量。從概念上講,訊號量維護了一個許可集合。如有必要,在許可可用前會阻塞每一個 acquire(),然後再擷取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的擷取者。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動。
    Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目。例如,下面的類使用訊號量控制對內容池的訪問:
    這裡是一個實際的情況,大家排隊上廁所,廁所只有兩個位置,來了10個人需要排隊。
    import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;public class MySemaphore extends Thread {private Semaphore position;private int id;public MySemaphore(int i, Semaphore s) {this.id = i;this.position = s;}public void run() {try {//有沒有空廁所if (position.availablePermits() > 0) {System.out.println("顧客[" + this.id + "]進入廁所,有空位");}else {System.out.println("顧客[" + this.id + "]進入廁所,沒空位,排隊");}//擷取到空廁所了position.acquire();System.out.println("顧客[" + this.id + "]獲得坑位");//使用中...Thread.sleep((int) (Math.random() * 1000));System.out.println("顧客[" + this.id + "]使用完畢");//廁所使用完之後釋放position.release();}catch (Exception e) {e.printStackTrace();}}public static void main(String args[]) {ExecutorService list = Executors.newCachedThreadPool();Semaphore position = new Semaphore(2);//只有兩個廁所//有十個人for (int i = 0; i < 10; i++) {list.submit(new MySemaphore(i + 1, position));}list.shutdown();position.acquireUninterruptibly(2);System.out.println("使用完畢,需要清掃了");position.release(2);}}

    ReentrantLockReentrantLock 將由最近成功獲得鎖定,並且還沒有釋放該鎖定的線程所擁有。當鎖定沒有被另一個線程所擁有時,調用 lock 的線程將成功擷取該鎖定並返回。如果當前線程已經擁有該鎖定,此方法將立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發生。
    此類的構造方法接受一個可選的公平參數。
    當設定為 true時,在多個線程的爭用下,這些鎖定傾向於將訪問權授予等待時間最長的線程。否則此鎖定將無法保證任何特定訪問順序。
    與採用預設設定(使用不公平鎖定)相比,使用公平鎖定的程式在許多線程訪問時表現為很低的總體輸送量(即速度很慢,常常極其慢),但是在獲得鎖定和保證鎖定分配的均衡性時差異較小。不過要注意的是,公平鎖定不能保證線程調度的公平性。因此,使用公平鎖定的眾多線程中的一員可能獲得多倍的成功機會,這種情況發生在其他活動線程沒有被處理並且目前並未持有鎖定時。還要注意的是,未定時的 tryLock 方法並沒有使用公平設定。因為即使其他線程正在等待,只要該鎖定是可用的,此方法就可以獲得成功。
    建議總是 立即實踐,使用 try 塊來調用 lock,在之前/之後的構造中,最典型的代碼如下:
    class X {private final ReentrantLock lock = new ReentrantLock();// ...public void m() {lock.lock(); // block until condition holdstry {// ... method body}finally {lock.unlock()}}}
    BlockingQueue支援兩個附加操作的 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 對象,並根據使用者擷取這些對象的時間來對它們進行解釋。
    下面的例子示範了這個阻塞隊列的準系統。

    import java.util.concurrent.BlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.LinkedBlockingQueue;public class MyBlockingQueue extends Thread {public static BlockingQueue<String> queue = new LinkedBlockingQueue<String>(3);private int index;public MyBlockingQueue(int i) {this.index = i;}public void run() {try {queue.put(String.valueOf(this.index));System.out.println("{" + this.index + "} in queue!");} catch (Exception e) {e.printStackTrace();}}public static void main(String args[]) {ExecutorService service = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {service.submit(new MyBlockingQueue(i));}Thread thread = new Thread() {public void run() {try {while (true) {Thread.sleep((int) (Math.random() * 1000));if (MyBlockingQueue.queue.isEmpty())break;String str = MyBlockingQueue.queue.take();System.out.println(str + " has take!");}} catch (Exception e) {e.printStackTrace();}}};service.submit(thread);service.shutdown();}}

    ---------------------執行結果-----------------
    {0} in queue!
    {1} in queue!
    {2} in queue!
    {3} in queue!
    0 has take!
    {4} in queue!
    1 has take!
    {6} in queue!
    2 has take!
    {7} in queue!
    3 has take!
    {8} in queue!
    4 has take!
    {5} in queue!
    6 has take!
    {9} in queue!
    7 has take!
    8 has take!
    5 has take!
    9 has take!
    -----------------------------------------

    CompletionService我們現在在Java中使用多線程通常不會直接用Thread對象了,而是會用到java.util.concurrent包下的ExecutorService類來初始化一個線程池供我們使用。之前我一直習慣自己維護一個list儲存submit的callable task所返回的Future對象。在主線程中遍曆這個list並調用Future的get()方法取到Task的傳回值。但是,我在很多地方會看到一些代碼通過CompletionService封裝ExecutorService,然後調用其take()方法去取Future對象。以前沒研究過這兩者之間的區別。今天看了原始碼之後就明白了。這兩者最主要的區別在於submit的task不一定是按照加入自己維護的list順序完成的。
    從list中遍曆的每個Future對象並不一定處於完成狀態,這時調用get()方法就會被阻塞住,如果系統是設計成每個線程完成後就能根據其結果繼續做後面的事,這樣對於處於list後面的但是先完成的線程就會增加了額外的等待時間。而CompletionService的實現是維護一個儲存Future對象的BlockingQueue。只有當這個Future對象狀態是結束的時候,才會加入到這個Queue中,take()方法其實就是Producer-Consumer中的Consumer。它會從Queue中取出Future對象,如果Queue是空的,就會阻塞在那裡,直到有完成的Future對象加入到Queue中。
    所以,先完成的必定先被取出。這樣就減少了不必要的等待時間。ExecutorCompletionService 類提供了此方法的一個實現。

    import java.util.concurrent.Callable;import java.util.concurrent.CompletionService;import java.util.concurrent.ExecutorCompletionService;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class MyCompletionService implements Callable<String> {private int id;public MyCompletionService(int i) {this.id = i;}public static void main(String[] args) throws Exception {ExecutorService service = Executors.newCachedThreadPool();CompletionService<String> completion = new ExecutorCompletionService<String>(service);for (int i = 0; i < 10; i++) {completion.submit(new MyCompletionService(i));}for (int i = 0; i < 10; i++) {System.out.println(completion.take().get());}service.shutdown();}public String call() throws Exception {Integer time = (int) (Math.random() * 1000);try {System.out.println(this.id + " start");Thread.sleep(time);System.out.println(this.id + " end");}catch (Exception e) {e.printStackTrace();}return this.id + ":" + time;}}

    CountDownLatch一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。
    用給定的計數 初始化 CountDownLatch。由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。
    之後,會釋放所有等待的線程,await 的所有後續調用都將立即返回。這種現象只出現一次——計數無法被重設。如果需要重設計數,請考慮使用 CyclicBarrier。
    CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,
    或入口:在通過調用 countDown() 的線程開啟入口前,所有調用 await 的線程都一直在入口處等待。
    用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。
    CountDownLatch 的一個有用特性是,它不要求調用 countDown 方法的線程等到計數到達零時才繼續,而在所有線程都能通過之前,它只是阻止任何線程繼續通過一個 await。

    下面的例子是別人寫的,非常形象。

    import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestCountDownLatch {public static void main(String[] args) throws InterruptedException {// 開始的倒數鎖final CountDownLatch begin = new CountDownLatch(1);// 結束的倒數鎖final CountDownLatch end = new CountDownLatch(10);// 十名選手final ExecutorService exec = Executors.newFixedThreadPool(10);for (int index = 0; index < 10; index++) {final int NO = index + 1;Runnable run = new Runnable() {public void run() {try {begin.await();// 一直阻塞Thread.sleep((long) (Math.random() * 10000));System.out.println("No." + NO + " arrived");} catch (InterruptedException e) {} finally {end.countDown();}}};exec.submit(run);}System.out.println("Game Start");begin.countDown();end.await();System.out.println("Game Over");exec.shutdown();}}

    CountDownLatch最重要的方法是countDown()和await(),前者主要是倒數一次,後者是等待倒數到0,如果沒有到達0,就只有阻塞等待了。

    CyclicBarrier一個同步輔助類,它允許一組線程互相等待,直到到達某個公用屏障點 (common barrier point)。
    在涉及一組固定大小的線程的程式中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrier 在釋放等待線程後可以重用,所以稱它為迴圈 的 barrier。
    CyclicBarrier 支援一個可選的 Runnable 命令,在一組線程中的最後一個線程到達之後(但在釋放所有線程之前),該命令只在每個屏障點運行一次。若在繼續所有參與線程之前更新共用狀態,此屏障操作 很有用。
    樣本用法:下面是一個在並行分解設計中使用 barrier 的例子,很經典的旅行團例子:

    import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestCyclicBarrier {// 徒步需要的時間: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhanprivate 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;}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 (InterruptedException e) {} catch (BrokenBarrierException e) {}}}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();}}

    CyclicBarrier最重要的屬性就是參與者個數,另外最要方法是await()。當所有線程都調用了await()後,就表示這些線程都可以繼續執行,否則就會等待。

    FutureFuture 表示非同步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。
    計算完成後只能使用 get 方法來檢索結果,如有必要,計算完成前可以阻塞此方法。取消則由 cancel 方法來執行。
    還提供了其他方法,以確定任務是正常完成還是被取消了。一旦計算完成,就不能再取消計算。
    如果為了可取消性而使用 Future但又不提供可用的結果,則可以聲明 Future<?> 形式類型、並返回 null 作為基礎任務的結果。
    這個我們在前面CompletionService已經看到了,這個Future的功能,而且這個可以在提交線程的時候被指定為一個返回對象的。看下面的例子:
    import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class MyFutureTask {/** * @param args * @throws InterruptedException  * @throws ExecutionException * @throws InterruptedException * @throws ExecutionException  */public static void main(String[] args) throws InterruptedException, ExecutionException {final ExecutorService exe=Executors.newFixedThreadPool(3);Callable<String> call=new Callable<String>(){public String call() throws InterruptedException {return "Thread is finished";}};Future<String> task=exe.submit(call);String obj=task.get();System.out.println(obj+"進程結束");System.out.println("總進程結束");exe.shutdown();}}class MyThreadTest implements Runnable {private String str;public MyThreadTest(String str) {this.str = str;}public void run() {this.setStr("allen"+str);}public void addString(String str) {this.str = "allen:" + str;}public String getStr() {return str;}public void setStr(String str) {this.str = str;}}

    ScheduledExecutorService一個 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 實現提供了便捷的Factory 方法。

    下面的例子也是網上比較流行的。

    import static java.util.concurrent.TimeUnit.SECONDS;import java.util.Date;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledFuture;public class TestScheduledThread {public static void main(String[] args) {final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);final Runnable beeper = new Runnable() {int count = 0;public void run() {System.out.println(new Date() + " beep " + (++count));}};// 1秒鐘後運行,並每隔2秒運行一次final ScheduledFuture beeperHandle = scheduler.scheduleAtFixedRate(beeper, 1, 2, SECONDS);// 2秒鐘後運行,並每次在上次任務運行完後等待5秒後重新運行final ScheduledFuture beeperHandle2 = scheduler.scheduleWithFixedDelay(beeper, 2, 5, SECONDS);// 30秒後結束關閉任務,並且關閉Schedulerscheduler.schedule(new Runnable() {public void run() {beeperHandle.cancel(true);beeperHandle2.cancel(true);scheduler.shutdown();}}, 30, SECONDS);}}

    另外,還有以下改進:

    1. 各種特定用途的容器,方便線程之間的通訊。如BlockingQueue,DelayQueue,ConcurrentHashMap,CopyOnWriteArrayList等。

    2. 安全執行緒的基本變數類,在包java.util.concurrent.atomic中提供。

    3. 計時。TimeUnit類為指定和控制基於逾時的操作提供了多重粒度(包括納秒級)。以代替簡陋的Thread.sleep。

java並發6-小結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.