併發工具箱 concurrent包的原理分析以及使用

併發工具箱 concurrent包的原理分析以及使用

1、java.util.concurrent 包下的類分類圖

 

J.U.C

  • locks部分:顯式鎖(互斥鎖和速寫鎖)相關;
  • atomic部分:原子變量類相關,是構建非阻塞算法的基礎;
  • executor部分:線程池相關;
  • collections部分:併發容器相關;
  • tools部分:同步工具相關,如信號量、閉鎖、柵欄等功能;

1、1 collections部分:

1、1、1  BlockingQueue

  BlockingQueue爲接口,如果要是他,需要使用實現他的子類;

  BlockingQueue的子類包括'

    ArrayBlockingQueue;

    DelayQueue;

    LinkedBlockingQueue;

    SynchronousQueue;

    PriorirtyBlockingQueue;

    TransferQueue;

  

BlockingQueue 用法

BlockingQueue 通常用於一個線程生產對象,而另外一個線程消費這些對象的場景。下圖是對這個原理的闡述:

一個線程往裏邊放,另外一個線程從裏邊取的一個 BlockingQueue。

一個線程將會持續生產新對象並將其插入到隊列之中,直到隊列達到它所能容納的臨界點。也就是說,它是有限的。如果該阻塞隊列到達了其臨界點,負責生產的線程將會在往裏邊插入新對象時發生阻塞。它會一直處於阻塞之中,直到負責消費的線程從隊列中拿走一個對象。負責消費的線程將會一直從該阻塞隊列中拿出對象。如果消費線程嘗試去從一個空的隊列中提取對象的話,這個消費線程將會處於阻塞之中,直到一個生產線程把一個對象丟進隊列。

BlockingQueue 的方法

BlockingQueue 具有 4 組不同的方法用於插入、移除以及對隊列中的元素進行檢查。如果請求的操作不能得到立即執行的話,每個方法的表現也不同。這些方法如下:

四組不同的行爲方式解釋:

拋異常:如果試圖的操作無法立即執行,拋一個異常。
特定值:如果試圖的操作無法立即執行,返回一個特定的值(常常是 true / false)。
阻塞:如果試圖的操作無法立即執行,該方法調用將會發生阻塞,直到能夠執行。
超時:如果試圖的操作無法立即執行,該方法調用將會發生阻塞,直到能夠執行,但等待時間不會超過給定值。返回一個特定值以告知該操作是否成功(典型的是 true / false)。

無法向一個 BlockingQueue 中插入 null。如果你試圖插入 null,BlockingQueue 將會拋出一個 NullPointerException。

可以訪問到 BlockingQueue 中的所有元素,而不僅僅是開始和結束的元素。比如說,你將一個對象放入隊列之中以等待處理,但你的應用想要將其取消掉。那麼你可以調用諸如 remove(o) 方法來將隊列之中的特定對象進行移除。但是這麼幹效率並不高(譯者注:基於隊列的數據結構,獲取除開始或結束位置的其他對象的效率不會太高),因此你儘量不要用這一類的方法,除非你確實不得不那麼做。

Java 中使用 BlockingQueue 的例子

這裏是一個 Java 中使用 BlockingQueue 的示例。本示例使用的是 BlockingQueue 接口的 ArrayBlockingQueue 實現。

首先,BlockingQueueExample 類分別在兩個獨立的線程中啓動了一個 Producer 和 一個 Consumer。

Producer 向一個共享的 BlockingQueue 中注入字符串,而 Consumer 則會從中把它們拿出來。

 

public class BlockingQueueExample { 
 
    public static void main(String[] args) throws Exception { 
 
        BlockingQueue queue = new ArrayBlockingQueue(1024); 
 
        Producer producer = new Producer(queue); 
        Consumer consumer = new Consumer(queue); 
 
        new Thread(producer).start(); 
        new Thread(consumer).start(); 
 
        Thread.sleep(4000); 
    } 
}

 

以下是 Producer 類。注意它在每次 put() 調用時是如何休眠一秒鐘的。這將導致 Consumer 在等待隊列中對象的時候發生阻塞。

public class Producer implements Runnable{ 
 
    protected BlockingQueue queue = null; 
 
    public Producer(BlockingQueue queue) { 
        this.queue = queue; 
    } 
 
    public void run() { 
        try { 
            queue.put("1"); //無需考慮安全問題 直接使用
            Thread.sleep(1000); 
            queue.put("2"); 
            Thread.sleep(1000); 
            queue.put("3"); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
    } 
}

 

 

以下是 Consumer 類。它只是把對象從隊列中抽取出來,然後將它們打印到 System.out。

public class Consumer implements Runnable{ 
 
    protected BlockingQueue queue = null; 
 
    public Consumer(BlockingQueue queue) { 
        this.queue = queue; 
    } 
 
    public void run() { 
        try { 
            System.out.println(queue.take()); 
            System.out.println(queue.take()); 
            System.out.println(queue.take()); 
        } catch (InterruptedException e) { 
            e.printStackTrace(); 
        } 
    } 
}

 

數組阻塞隊列 ArrayBlockingQueue

  ArrayBlockingQueue 類實現了 BlockingQueue 接口。

  ArrayBlockingQueue 是一個有界的阻塞隊列,其內部實現是將對象放到一個數組裏。有界也就意味着,它不能夠存儲無限多數量的元素。它有一個同一時間能夠存儲元素數量的上限。

  你可以在對其初始化的時候設定這個上限,但之後就無法對這個上限進行修改了(譯者注:因爲它是基於數組實現的,也就具有數組的特性:一旦初始化,大小就無法修改)。

  ‘ArrayBlockingQueue 內部以 FIFO(先進先出)的順序對元素進行存儲。隊列中的頭元素在所有元素之中是放入時間最久的那個,而尾元素則是最短的那個。

  ArrayBlockingQueue的使用代碼案例見上例代碼。

4. 延遲隊列 DelayQueue

DelayQueue 實現了 BlockingQueue 接口。DelayQueue 對元素進行持有直到一個特定的延遲到期。注入其中的元素必須實現 java.util.concurrent.Delayed 接口,該接口定義:

public interface Delayed extends Comparable<Delayed< { 
 
 public long getDelay(TimeUnit timeUnit);  //注意此處的TimeUnit的使用
 
}

 

DelayQueue 將會在每個元素的 getDelay() 方法返回的值的時間段之後才釋放掉該元素。如果返回的是 0 或者負值,延遲將被認爲過期,該元素將會在 DelayQueue 的下一次 take 被調用的時候被釋放掉。

也就是說通過getDelay返回元素到期時間,只有元素在隊列中存在的時間超過該時間後,纔可以在延遲隊列取出該對象;

傳遞給 getDelay 方法的 getDelay 實例是一個枚舉類型,它表明了將要延遲的時間段。

TimeUnit 枚舉將會取以下值:

DAYS 
HOURS 
MINUTES 
SECONDS 
MILLISECONDS 
MICROSECONDS 
NANOSECONDS

 

正如你所看到的,Delayed 接口也繼承了 java.lang.Comparable 接口,這也就意味着 Delayed 對象之間可以進行對比。這個在對 DelayQueue 隊列中的元素進行添加時排序,因此它們可以根據過期時間進行有序釋放。以下是使用 DelayQueue 的例子:

 

DelayQueue的兩個應用案例:【注意該案例中用到了 concrrent 包下的 tools 下的  countDownLatch 如果對該部分不理解 先看 1.2 部門的 Tools 】

 

1、2  Tools部分:

  CountDownLatch用法

  CountDownLatch類位於java.util.concurrent包下,利用它可以實現類似計數器的功能。比如有一個任務A,它要等待其他4個任務執行完畢之後才能執行,此時就可以利用CountDownLatch來實現這種功能了。

  通俗一點的說 就是 使用CountDownLatch 時  幾個線程使用同一個CountDownLatch對象 有一個線程調用wait方法  其他線程使用countDown()方法,當最後計數器減到0的時候,wait調用處 繼續往下執行;

  CountDownLatch類只提供了一個構造器:

 

public CountDownLatch(int count) {  };  //參數count爲計數值

 

然後下面這3個方法是CountDownLatch類中最重要的方法:

public void await() throws InterruptedException { };   //調用await()方法的線程會被掛起,它會等待直到count值爲0才繼續執行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()類似,只不過等待一定的時間後count值還沒變爲0的話就會繼續執行
public void countDown() { };  //將count值減1

下面看使用範例:

public class Test {
     public static void main(String[] args) {   
         final CountDownLatch latch = new CountDownLatch(2);
 
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
                    Thread.sleep(3000);
                    System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
 
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
                     Thread.sleep(3000);
                     System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
 
         try {
             System.out.println("等待2個子線程執行完畢...");
            latch.await();
            System.out.println("2個子線程已經執行完畢");
            System.out.println("繼續執行主線程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     }
}

執行結果;

線程Thread-0正在執行
線程Thread-1正在執行
等待2個子線程執行完畢...
線程Thread-0執行完畢
線程Thread-1執行完畢
2個子線程已經執行完畢
繼續執行主線程

 

CyclicBarrier用法

字面意思迴環柵欄,通過它可以實現讓一組線程等待至某個狀態之後再全部同時執行。叫做迴環是因爲當所有等待線程都被釋放以後,CyclicBarrier可以被重用。我們暫且把這個狀態就叫做barrier,當調用await()方法之後,線程就處於barrier了。

通俗的講 就是 使用 CyclicBrarrier 幾個線程在執行到需要進行柵欄的地方時 調用 CyclicBrarrier 對象的 wait 方法 然後當所有的對象都執行到柵欄的地方後 統一進行放行 往後執行;

CyclicBarrier類位於java.util.concurrent包下,CyclicBarrier提供2個構造器:

public CyclicBarrier(int parties, Runnable barrierAction) {
}
 
public CyclicBarrier(int parties) {
}

參數parties指讓多少個線程或者任務等待至barrier狀態;

參數barrierAction爲當這些線程都達到barrier狀態時會執行的內容;其實就是 當所有線程到達柵欄後,如果需要執行在放行前需要執行一個其他操作,可以實現runnable的run方法,此時會調用一個線程執行run方法;

然後CyclicBarrier中最重要的方法就是await方法,它有2個重載版本:

public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };

第一個版本比較常用,用來掛起當前線程,直至所有線程都到達barrier狀態再同時執行後續任務;

第二個版本是讓這些線程等待至一定的時間,如果還有線程沒有到達barrier狀態就直接讓到達barrier的線程執行後續任務。

下面舉幾個例子就明白了:

假若有若干個線程都要進行寫數據操作,並且只有所有線程都完成寫數據操作之後,這些線程才能繼續做後面的事情,此時就可以利用CyclicBarrier了:

 

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入數據操作
                System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
                cyclicBarrier.await(); //柵欄的地方 所有線程停留在此處等待其他線程執行到此
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有線程寫入完畢,繼續處理其他任務...");
        }
    }
}

 

測試結果:

 

線程Thread-0正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...

 

  從上面輸出結果可以看出,每個寫入線程執行完寫數據操作之後,就在等待其他線程寫入操作完畢。

  當所有線程線程寫入操作完畢之後,所有線程就繼續進行後續的操作了。

  如果說想在所有線程寫入操作完之後,進行額外的其他操作可以爲CyclicBarrier提供Runnable參數:

 

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {
            @Override
            public void run() {
                System.out.println("當前線程"+Thread.currentThread().getName());   
            }
        });
 
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入數據操作
                System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有線程寫入完畢,繼續處理其他任務...");
        }
    }
}

 

測試結果:

 

線程Thread-0正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
當前線程Thread-3
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...

 

 

當柵欄處的wait方法使用帶有設定時間參數的時候,當等待Barrier過程中 超過時間後  就拋出異常並繼續執行後面的任務。

案例代碼:

 

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
 
        for(int i=0;i<N;i++) {
            if(i<N-1)
                new Writer(barrier).start();
            else {                                //最後一個線程等待5000毫秒後再執行,使得其他線程會發生超時 拋出異常
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                new Writer(barrier).start();
            }
        }
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入數據操作
                System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
                try {
                    cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);
                } catch (TimeoutException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"所有線程寫入完畢,繼續處理其他任務...");
        }
    }
}

 

 

線程Thread-0正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
線程Thread-3正在寫入數據...
java.util.concurrent.TimeoutException
Thread-1所有線程寫入完畢,繼續處理其他任務...
Thread-0所有線程寫入完畢,繼續處理其他任務...
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
java.util.concurrent.BrokenBarrierException
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
java.util.concurrent.BrokenBarrierException
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
Thread-2所有線程寫入完畢,繼續處理其他任務...
java.util.concurrent.BrokenBarrierException
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
    at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)
    at java.util.concurrent.CyclicBarrier.await(Unknown Source)
    at com.cxh.test1.Test$Writer.run(Test.java:58)
Thread-3所有線程寫入完畢,繼續處理其他任務...

 

 

另外CyclicBarrier是可以重用的,看下面這個例子:

 

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
 
        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }
 
        try {
            Thread.sleep(25000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        System.out.println("CyclicBarrier重用");
 
        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入數據操作
                System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
 
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"所有線程寫入完畢,繼續處理其他任務...");
        }
    }
}

 

測試結果:

 

線程Thread-0正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
Thread-0所有線程寫入完畢,繼續處理其他任務...
Thread-3所有線程寫入完畢,繼續處理其他任務...
Thread-1所有線程寫入完畢,繼續處理其他任務...
Thread-2所有線程寫入完畢,繼續處理其他任務...
CyclicBarrier重用
線程Thread-4正在寫入數據...
線程Thread-5正在寫入數據...
線程Thread-6正在寫入數據...
線程Thread-7正在寫入數據...
線程Thread-7寫入數據完畢,等待其他線程寫入完畢
線程Thread-5寫入數據完畢,等待其他線程寫入完畢
線程Thread-6寫入數據完畢,等待其他線程寫入完畢
線程Thread-4寫入數據完畢,等待其他線程寫入完畢
Thread-4所有線程寫入完畢,繼續處理其他任務...
Thread-5所有線程寫入完畢,繼續處理其他任務...
Thread-6所有線程寫入完畢,繼續處理其他任務...
Thread-7所有線程寫入完畢,繼續處理其他任務...

 

Semaphore用法

  Semaphore翻譯成字面意思爲 信號量,Semaphore可以控同時訪問的線程個數,通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可。

  Semaphore類位於java.util.concurrent包下,它提供了2個構造器:

 

public Semaphore(int permits) {          //參數permits表示許可數目,即同時可以允許多少線程進行訪問
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {    //這個多了一個參數fair表示是否是公平的,即等待時間越久的越先獲取許可
    sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}

 

下面說一下Semaphore類中比較重要的幾個方法,首先是acquire()、release()方法:

public void acquire() throws InterruptedException {  }     //獲取一個許可
public void acquire(int permits) throws InterruptedException { }    //獲取permits個許可
public void release() { }          //釋放一個許可
public void release(int permits) { }    //釋放permits個許可

acquire()用來獲取一個許可,若無許可能夠獲得,則會一直等待,直到獲得許可。

release()用來釋放許可。注意,在釋放許可之前,必須先獲獲得許可。

這4個方法都會被阻塞,如果想立即得到執行結果,可以使用下面幾個方法

public boolean tryAcquire() { };    //嘗試獲取一個許可,若獲取成功,則立即返回true,若獲取失敗,則立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };  //嘗試獲取一個許可,若在指定的時間內獲取成功,則立即返回true,否則則立即返回false
public boolean tryAcquire(int permits) { }; //嘗試獲取permits個許可,若獲取成功,則立即返回true,若獲取失敗,則立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //嘗試獲取permits個許可,若在指定的時間內獲取成功,則立即返回true,否則則立即返回false

下面通過一個例子來看一下Semaphore的具體使用:

假若一個工廠有5臺機器,但是有8個工人,一臺機器同時只能被一個工人使用,只有使用完了,其他工人才能繼續使用。那麼我們就可以通過Semaphore來實現:【acquire 是阻塞式等待獲得執行許可】

 

public class Test {
    public static void main(String[] args) {
        int N = 8;            //工人數
        Semaphore semaphore = new Semaphore(5); //機器數目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }
 
    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }
 
        @Override
        public void run() {
            try {
                semaphore.acquire(); //如果新線程進入後不能夠獲得許可  則會阻塞 直到獲得許可然後再往下執行
          //獲得到使用資源的能力 併發執行的線程數 取決於 semaphore 的個數配置  此配置數也就是 可用資源的數目

                System.out.println("工人"+this.num+"佔用一個機器在生產...");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"釋放出機器");
                semaphore.release();    //釋放許可       
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

測試結果:

 

工人0佔用一個機器在生產...
工人1佔用一個機器在生產...
工人2佔用一個機器在生產...
工人4佔用一個機器在生產...
工人5佔用一個機器在生產...
工人0釋放出機器
工人2釋放出機器
工人3佔用一個機器在生產...
工人7佔用一個機器在生產...
工人4釋放出機器
工人5釋放出機器
工人1釋放出機器
工人6佔用一個機器在生產...
工人3釋放出機器
工人7釋放出機器
工人6釋放出機器

 

 

exchanger 用法

  Exchanger是在兩個任務之間交換對象的柵欄,當這些任務進入柵欄時,它們各自擁有一個對象。當他們離開時,它們都擁有之前由對象持有的對象。

  它典型的應用場景是:一個任務在創建對象,這些對象的生產代價很高昂,而另一個任務在消費這些對象。通過這種方式,可以有更多的對象在被創建的同時被消費。

 

public class ExchangerTest {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        final Exchanger exchanger = new Exchanger();
        executor.execute(new Runnable() {
            String data1 = "Ling";

            @Override
            public void run() {
                doExchangeWork(data1, exchanger);
            }
        });

        executor.execute(new Runnable() {
            String data2 = "huhx";

            @Override
            public void run() {
                doExchangeWork(data2, exchanger);
            }
        });
        executor.shutdown(); //把線程池停掉 釋放資源
    }

    private static void doExchangeWork(String data1, Exchanger exchanger) {
        try {
            System.out.println(Thread.currentThread().getName() + "正在把數據 " + data1 + " 交換出去");
            Thread.sleep((long) (Math.random() * 1000));

            String data2 = (String) exchanger.exchange(data1);
            System.out.println(Thread.currentThread().getName() + "交換數據 到  " + data2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

 

 

 

Tools 總結:

下面對上面說的三個輔助類進行一個總結:

1)CountDownLatch和CyclicBarrier都能夠實現線程之間的等待,只不過它們側重點不同:

CountDownLatch一般用於某個線程A等待若干個其他線程執行完任務之後,它才執行;

而CyclicBarrier一般用於一組線程互相等待至某個狀態,然後這一組線程再同時執行;

另外,CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。

2)Semaphore其實和鎖有點類似,它一般用於控制對某組資源的訪問權限。

 

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