Java多執行緒程式設計之同步器

sanchan_yan發表於2016-08-16

同步器

為每種特定的同步問題提供瞭解決方案

Semaphore

Semaphore【訊號標;旗語】,通過計數器控制對共享資源的訪問。

測試類:

    package concurrent;

    import concurrent.thread.SemaphoreThread;

    import java.util.concurrent.Semaphore;

    /**
    * 拿客
    * www.coderknock.com
    * QQ群:213732117
    * 建立時間:2016年08月08日
    * 描述:
    */
    public class SemaphoreTest {

       public static void main(String[] args) {
           //在Thread裡宣告並不是同一個物件
           Semaphore semaphore = new Semaphore(3);
           SemaphoreThread testA = new SemaphoreThread("A", semaphore);
           SemaphoreThread testB = new SemaphoreThread("B", semaphore);
           SemaphoreThread testC = new SemaphoreThread("C", semaphore);
           SemaphoreThread testD = new SemaphoreThread("D", semaphore);
           SemaphoreThread testE = new SemaphoreThread("E", semaphore);
           SemaphoreThread testF = new SemaphoreThread("F", semaphore);
           SemaphoreThread testG = new SemaphoreThread("G", semaphore);
           testA.start();
           testB.start();
           testC.start();
           testD.start();
           testE.start();
           testF.start();
           testG.start();
       }
   }

執行緒寫法:

   package concurrent.thread;

   import org.apache.logging.log4j.LogManager;
   import org.apache.logging.log4j.Logger;

   import java.util.concurrent.Semaphore;

   /**
    * 拿客
    * www.coderknock.com
    * QQ群:213732117
    * 建立時間:2016年08月08日
    * 描述:
    */
   public class SemaphoreThread extends Thread {
       private static final Logger logger = LogManager.getLogger(SemaphoreThread.class);
       //建立有3個訊號量的訊號量計數器
       public Semaphore semaphore;

       public SemaphoreThread(String name, Semaphore semaphore) {
           setName(name);
           this.semaphore = semaphore;
       }

       @Override
       public void run() {
           try {
               logger.debug(getName() + " 取號等待... " + System.currentTimeMillis());
               //取出一個訊號
               semaphore.acquire();
               logger.debug(getName() + " 提供服務... " + System.currentTimeMillis());
               sleep(1000);
               logger.debug(getName() + " 完成服務... " + System.currentTimeMillis());

           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           logger.debug(getName() + " 釋放... " + System.currentTimeMillis());
           //釋放一個訊號
           semaphore.release();
       }
   }

執行結果【以下所有輸出結果中[]中為執行緒名稱- 後為輸出的內容】:

    [C] - C 取號等待... 1470642024037
    [F] - F 取號等待... 1470642024036
    [E] - E 取號等待... 1470642024036
    [B] - B 取號等待... 1470642024037
    [D] - D 取號等待... 1470642024037
    [A] - A 取號等待... 1470642023965
    [D] - D 提供服務... 1470642024039
    [C] - C 提供服務... 1470642024039
    [G] - G 取號等待... 1470642024036
    [F] - F 提供服務... 1470642024040
    [D] - D 完成服務... 1470642025039
    [C] - C 完成服務... 1470642025039
    [D] - D 釋放... 1470642025040
    [F] - F 完成服務... 1470642025040
    [C] - C 釋放... 1470642025041
    [B] - B 提供服務... 1470642025042
    [A] - A 提供服務... 1470642025042
    [F] - F 釋放... 1470642025043
    [E] - E 提供服務... 1470642025043
    [A] - A 完成服務... 1470642026043
    [B] - B 完成服務... 1470642026043
    [B] - B 釋放... 1470642026043
    [A] - A 釋放... 1470642026043
    [G] - G 提供服務... 1470642026044
    [E] - E 完成服務... 1470642026045
    [E] - E 釋放... 1470642026045
    [G] - G 完成服務... 1470642027045
    [G] - G 釋放... 1470642027046

可以看到,當3個訊號量被領取完之後,之後的執行緒會阻塞在領取訊號的位置,當有訊號量釋放之後才會繼續執行。

CountDownLatch

CountDownLatch【倒數計時鎖】,執行緒中呼叫countDownLatch.await()使程式進入阻塞狀態,當達成指定次數後(通過countDownLatch.countDown())繼續執行每個執行緒中剩餘的內容。

測試類:

package concurrent.thread;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.CountDownLatch;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class package concurrent;

import concurrent.thread.CountDownLatchThread;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class CountDownLatchTest {

    private static final Logger logger = LogManager.getLogger(CountDownLatchTest.class);

    public static void main(String[] args) throws InterruptedException {
        //設定當達成三個計數時觸發
        CountDownLatch countDownLatch = new CountDownLatch(3);
        new CountDownLatchThread("A", countDownLatch).start();
        new CountDownLatchThread("B", countDownLatch).start();
        new CountDownLatchThread("C", countDownLatch).start();
        new CountDownLatchThread("D", countDownLatch).start();
        new CountDownLatchThread("E", countDownLatch).start();
        for (int i = 3; i > 0; i--) {
            Thread.sleep(1000);
            logger.debug(i);
            countDownLatch.countDown();
        }
    }
}

執行緒類:

package concurrent.thread;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.CountDownLatch;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class CountDownLatchThread extends Thread {
    private static final Logger logger = LogManager.getLogger(CountDownLatchThread.class);
    //計數器
    private CountDownLatch countDownLatch;

    public CountDownLatchThread(String name, CountDownLatch countDownLatch) {
        setName(name);
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        logger.debug("執行操作...");
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.debug("等待計數器達到標準...");
        try {
            //讓執行緒進入阻塞狀態,等待計數達成後釋放
            countDownLatch.await();
            logger.debug("計數達成,繼續執行...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行結果:

 [E] - 執行操作...
 [B] - 執行操作...
 [A] - 執行操作...
 [C] - 執行操作...
 [D] - 執行操作...
 [main] DEBUG concurrent.CountDownLatchTest - 3
 [B] - 等待計數器達到標準...
 [E] - 等待計數器達到標準...
 [C] - 等待計數器達到標準...
 [D] - 等待計數器達到標準...
 [A] - 等待計數器達到標準...
 [main] DEBUG concurrent.CountDownLatchTest - 2
 [main] DEBUG concurrent.CountDownLatchTest - 1
 [E] - 計數達成,繼續執行...
 [C] - 計數達成,繼續執行...
 [B] - 計數達成,繼續執行...
 [D] - 計數達成,繼續執行...
 [A] - 計數達成,繼續執行...
CyclicBarrier

CyclicBarrier【Cyclic週期,迴圈的 Barrier屏障,障礙】迴圈的等待阻塞的執行緒個數到達指定數量後使參與計數的執行緒繼續執行並可執行特定執行緒(使用不同建構函式可以不設定到達後執行),其他執行緒仍處於阻塞等待再一次達成指定個數。

測試類:

package concurrent;

import concurrent.thread.CyclicBarrierThread;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.CyclicBarrier;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class CyclicBarrierTest {

    private static final Logger logger = LogManager.getLogger(CyclicBarrierTest.class);

    public static void main(String[] args) {
          //可以使用CyclicBarrier(int parties)不設定到達後執行的內容
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            logger.debug("---計數到達後執行的內容----");
        });
        new CyclicBarrierThread("A", cyclicBarrier).start();
        new CyclicBarrierThread("B", cyclicBarrier).start();
        new CyclicBarrierThread("C", cyclicBarrier).start();
        new CyclicBarrierThread("D", cyclicBarrier).start();
        new CyclicBarrierThread("E", cyclicBarrier).start();
        new CyclicBarrierThread("A2", cyclicBarrier).start();
        new CyclicBarrierThread("B2", cyclicBarrier).start();
        new CyclicBarrierThread("C2", cyclicBarrier).start();
        new CyclicBarrierThread("D2", cyclicBarrier).start();
        new CyclicBarrierThread("E2", cyclicBarrier).start();
        //需要注意的是,如果執行緒數不是上面設定的等待數量的整數倍,比如這個程式中又加了個執行緒,
        // 那麼當達到5個數量時,只會執行達到時的五個執行緒的內容,
        // 剩餘一個執行緒會出於阻塞狀態導致主執行緒無法退出,程式無法結束
        // new CyclicBarrierThread("F", cyclicBarrier).start();//將這行註釋去掉程式無法自動結束
    }
}

執行緒類:

package concurrent.thread;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class CyclicBarrierThread extends Thread {

    private static final Logger logger = LogManager.getLogger(CyclicBarrierThread.class);

    private CyclicBarrier cyclicBarrier;

    public CyclicBarrierThread(String name, CyclicBarrier cyclicBarrier) {
        super(name);
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        logger.debug("執行操作...");
        try {
            int time = new Random().nextInt(10) * 1000;
            logger.debug("休眠" + time/1000 + "秒");
            sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.debug("等待計數器達到標準...");
        try {
            //讓執行緒進入阻塞狀態,等待計數達成後釋放
            cyclicBarrier.await();
            logger.debug("計數達成,繼續執行...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

執行結果:

 [A] - 執行操作...
 [A] - 休眠0秒
 [E2] - 執行操作...
 [E2] - 休眠5秒
 [D2] - 執行操作...
 [D2] - 休眠4秒
 [C2] - 執行操作...
 [C2] - 休眠4秒
 [B2] - 執行操作...
 [B2] - 休眠6秒
 [A2] - 執行操作...
 [A2] - 休眠8秒
 [E] - 執行操作...
 [E] - 休眠5秒
 [D] - 執行操作...
 [D] - 休眠0秒
 [C] - 執行操作...
 [C] - 休眠3秒
 [B] - 執行操作...
 [B] - 休眠7秒
 [A] - 等待計數器達到標準...
 [D] - 等待計數器達到標準...
 [C] - 等待計數器達到標準...
 [D2] - 等待計數器達到標準...
 [C2] - 等待計數器達到標準...
 [C2] DEBUG concurrent.CyclicBarrierTest - ---計數到達後執行的內容----
 [C2] - 計數達成,繼續執行...
 [A] - 計數達成,繼續執行...
 [C] - 計數達成,繼續執行...
 [D2] - 計數達成,繼續執行...
 [D] - 計數達成,繼續執行...
 [E2] - 等待計數器達到標準...
 [E] - 等待計數器達到標準...
 [B2] - 等待計數器達到標準...
 [B] - 等待計數器達到標準...
 [A2] - 等待計數器達到標準...
 [A2] DEBUG concurrent.CyclicBarrierTest - ---計數到達後執行的內容----
 [E] - 計數達成,繼續執行...
 [B2] - 計數達成,繼續執行...
 [E2] - 計數達成,繼續執行...
 [B] - 計數達成,繼續執行...
 [A2] - 計數達成,繼續執行...

可以想象成以前不正規的長途汽車站的模式:

不正規的長途汽車站會等待座位坐滿之後才發車,到達目的地之後繼續等待然後迴圈進行。每個人都是一個Thread,上車後觸發cyclicBarrier.await();,當坐滿時就是達到指定達成數的時候,車輛發車就是達成後統一執行的內容,發車後車上的人們就可以聊天之類的操作了【我們暫且理解為上車後人們就都不能動了O(∩_∩)O~】。

CountDownLatch與CyclicBarrier區別:

CountDownLatch是一個或多個執行緒等待計數達成後繼續執行,await()呼叫並沒有參與計數。

CyclicBarrier則是N個執行緒等待彼此執行到零界點之後再繼續執行,await()呼叫的同時參與了計數,並且CyclicBarrier支援條件達成後執行某個動作,而且這個過程是迴圈性的。

Exchanger

Exchanger<T> 用於執行緒間進行資料交換

測試類:

package concurrent;

import concurrent.pojo.ExchangerPojo;
import concurrent.thread.ExchangerThread;

import java.util.HashMap;
import java.util.concurrent.Exchanger;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class ExchangerTest {

    public static void main(String[] args) {
        Exchanger<HashMap<String, ExchangerPojo>> exchanger = new Exchanger<>();
        new ExchangerThread("A", exchanger).start();
        new ExchangerThread("B", exchanger).start();
    }
}

實體類:

package concurrent.pojo;

import com.alibaba.fastjson.JSON;

import java.util.Date;
import java.util.List;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class ExchangerPojo {
    private int intVal;
    private String strVal;
    private List<String> strList;
    private Date date;

    public ExchangerPojo(int intVal, String strVal, List<String> strList, Date date) {
        this.intVal = intVal;
        this.strVal = strVal;
        this.strList = strList;
        this.date = date;
    }

    public int getIntVal() {
        return intVal;
    }

    public void setIntVal(int intVal) {
        this.intVal = intVal;
    }

    public String getStrVal() {
        return strVal;
    }

    public void setStrVal(String strVal) {
        this.strVal = strVal;
    }

    public List<String> getStrList() {
        return strList;
    }

    public void setStrList(List<String> strList) {
        this.strList = strList;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }
}

執行緒類:

package concurrent.thread;

import concurrent.pojo.ExchangerPojo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;
import java.util.concurrent.Exchanger;

/**
 * 拿客
 * www.coderknock.com
 * QQ群:213732117
 * 建立時間:2016年08月08日
 * 描述:
 */
public class ExchangerThread extends Thread {
    private Exchanger<HashMap<String, ExchangerPojo>> exchanger;

    private static final Logger logger = LogManager.getLogger(ExchangerThread.class);

    public ExchangerThread(String name, Exchanger<HashMap<String, ExchangerPojo>> exchanger) {
        super(name);
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        HashMap<String, ExchangerPojo> map = new HashMap<>();
        logger.debug(getName() + "提供者提供資料...");
        Random random = new Random();
        for (int i = 0; i < 3; i++) {
            int index = random.nextInt(10);
            List<String> list = new ArrayList<>();
            for (int j = 0; j < index; j++) {
                list.add("list ---> " + j);
            }
            ExchangerPojo pojo = new ExchangerPojo(index, getName() + "提供的資料", list, new Date());
            map.put("第" + i + "個資料", pojo);
        }
        try {
            int time = random.nextInt(10);
            logger.debug(getName() + "等待" + time + "秒....");
            for (int i = time; i > 0; i--) {
                sleep(1000);
                logger.debug(getName() + "---->" + i);
            }
              //等待exchange是會進入阻塞狀態,可以在一個執行緒中與另一執行緒多次互動,此處就不寫多次了
            HashMap<String, ExchangerPojo> getMap = exchanger.exchange(map);
            time = random.nextInt(10);
            logger.debug(getName() + "接受到資料等待" + time + "秒....");
            for (int i = time; i > 0; i--) {
                sleep(1000);
                logger.debug(getName() + "---->" + i);
            }
            getMap.forEach((x, y) -> {
                logger.debug(x + " -----> " + y.toString());
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

執行結果:

 [B] - B提供者提供資料...
 [A] - A提供者提供資料...
 [A] - A等待2秒....
 [B] - B等待0秒....
 [A] - A---->2
 [A] - A---->1
 [B] - B接受到資料等待1秒....
 [A] - A接受到資料等待4秒....
 [B] - B---->1
 [A] - A---->4
 [B] - 第0個資料 -----> {"date":1470652252049,"intVal":5,"strList":["list ---> 0","list ---> 1","list ---> 2","list ---> 3","list ---> 4"],"strVal":"A提供的資料"}
 [B] - 第1個資料 -----> {"date":1470652252049,"intVal":1,"strList":["list ---> 0"],"strVal":"A提供的資料"}
 [B] - 第2個資料 -----> {"date":1470652252049,"intVal":4,"strList":["list ---> 0","list ---> 1","list ---> 2","list ---> 3"],"strVal":"A提供的資料"}
 [A] - A---->3
 [A] - A---->2
 [A] - A---->1
 [A] - 第0個資料 -----> {"date":1470652252057,"intVal":1,"strList":["list ---> 0"],"strVal":"B提供的資料"}
 [A] - 第1個資料 -----> {"date":1470652252057,"intVal":6,"strList":["list ---> 0","list ---> 1","list ---> 2","list ---> 3","list ---> 4","list ---> 5"],"strVal":"B提供的資料"}
 [A] - 第2個資料 -----> {"date":1470652252057,"intVal":6,"strList":["list ---> 0","list ---> 1","list ---> 2","list ---> 3","list ---> 4","list ---> 5"],"strVal":"B提供的資料"}

Phaser

Phaser個人感覺兼具了CountDownLatch與CyclicBarrier的功能,並提供了分階段的能力。

實現分階段的CyclicBarrier的功能

測試程式碼:

package concurrent;

import concurrent.thread.PhaserThread;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.Phaser;

/**
 * 拿客
 * 網站:www.coderknock.com
 * QQ群:213732117
 * 三產 建立於 2016年08月08日 21:25:30。
 */
public class PhaserTest {

    private static final Logger logger = LogManager.getLogger(PhaserTest.class);

    public static void main(String[] args) {
        Phaser phaser = new Phaser() {
            /**此方法有2個作用:
             * 1、當每一個階段執行完畢,此方法會被自動呼叫,因此,過載此方法寫入的程式碼會在每個階段執行完畢時執行,相當於CyclicBarrier的barrierAction。
             * 2、當此方法返回true時,意味著Phaser被終止,因此可以巧妙的設定此方法的返回值來終止所有執行緒。例如:若此方法返回值為 phase>=3,其含義為當整個執行緒執行了4個階段後,程式終止。
             * */
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                logger.debug("階段--->" + phase);
                logger.debug("註冊的執行緒數量--->" + registeredParties);
                return super.onAdvance(phase, registeredParties);
            }
        };

        for (int i = 3; i > 0; i--) {
            new PhaserThread("第" + i + "個", phaser).start();
        }
    }
}

執行緒程式碼:

package concurrent.thread;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Random;
import java.util.concurrent.Phaser;

/**
 * 拿客
 * 網站:www.coderknock.com
 * QQ群:213732117
 * 三產 建立於 2016年08月08日 21:16:55。
 */
public class PhaserThread extends Thread {

    private Phaser phaser;

    private static final Logger logger = LogManager.getLogger(PhaserThread.class);

    public PhaserThread(String name, Phaser phaser) {
        super(name);
        this.phaser = phaser;
        //把當前執行緒註冊到Phaser
        this.phaser.register();
        logger.debug("name為" + name + "的執行緒註冊了" + this.phaser.getRegisteredParties() + "個執行緒");
    }

    @Override
    public void run() {
        logger.debug("進入...");
        phaser.arrive();
        for (int i = 6; i > 0; i--) {
            int time = new Random().nextInt(5);
            try {
                logger.debug("睡眠" + time + "秒");
                sleep(time * 1000);
                if (i == 1) {
                    logger.debug("未完成的執行緒數量:" + phaser.getUnarrivedParties());
                    logger.debug("最後一次觸發,並登出自身");
                    phaser.arriveAndDeregister();
                    logger.debug("未完成的執行緒數量:" + phaser.getUnarrivedParties());
                } else {
                    logger.debug("未完成的執行緒數量:" + phaser.getUnarrivedParties());
                    logger.debug(i + "--->觸發並阻塞...");
                    phaser.arriveAndAwaitAdvance();//相當於CyclicBarrier.await();
                    logger.debug("未完成的執行緒數量:" + phaser.getUnarrivedParties());
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        logger.debug("登出完成之後註冊的執行緒數量--->" + phaser.getRegisteredParties());
    }
}

執行結果:

 [main] - name為第3個的執行緒註冊了1個執行緒
 [main] - name為第2個的執行緒註冊了2個執行緒
 [main] - name為第1個的執行緒註冊了3個執行緒
 [第3個] - 進入...
 [第2個] - 進入...
 [第3個] - 睡眠2秒
 [第2個] - 睡眠1秒
 [第1個] - 進入...
 [第1個] - 階段--->0
 [第1個] - 註冊的執行緒數量--->3
 [第1個] - 睡眠4秒
 [第2個] - 未完成的執行緒數量:3
 [第2個] - 6--->觸發並阻塞...
 [第3個] - 未完成的執行緒數量:2
 [第3個] - 6--->觸發並阻塞...
 [第1個] - 未完成的執行緒數量:1
 [第1個] - 6--->觸發並阻塞...
 [第1個] - 階段--->1
 [第1個] - 註冊的執行緒數量--->3
 [第1個] - 未完成的執行緒數量:3
 [第3個] - 未完成的執行緒數量:3
 [第2個] - 未完成的執行緒數量:3
 [第1個] - 睡眠1秒
 [第3個] - 睡眠0秒
 [第2個] - 睡眠4秒
 [第3個] - 未完成的執行緒數量:3
 [第3個] - 5--->觸發並阻塞...
 [第1個] - 未完成的執行緒數量:2
 [第1個] - 5--->觸發並阻塞...
 [第2個] - 未完成的執行緒數量:1
 [第2個] - 5--->觸發並阻塞...
 [第2個] - 階段--->2
 [第2個] - 註冊的執行緒數量--->3
 [第2個] - 未完成的執行緒數量:3
 [第3個] - 未完成的執行緒數量:3
 [第1個] - 未完成的執行緒數量:3
 [第2個] - 睡眠0秒
 [第3個] - 睡眠2秒
 [第2個] - 未完成的執行緒數量:3
 [第1個] - 睡眠2秒
 [第2個] - 4--->觸發並阻塞...
 [第3個] - 未完成的執行緒數量:2
 [第1個] - 未完成的執行緒數量:2
 [第3個] - 4--->觸發並阻塞...
 [第1個] - 4--->觸發並阻塞...
 [第1個] - 階段--->3
 [第1個] - 註冊的執行緒數量--->3
 [第1個] - 未完成的執行緒數量:3
 [第3個] - 未完成的執行緒數量:3
 [第2個] - 未完成的執行緒數量:3
 [第1個] - 睡眠2秒
 [第3個] - 睡眠1秒
 [第2個] - 睡眠4秒
 [第3個] - 未完成的執行緒數量:3
 [第3個] - 3--->觸發並阻塞...
 [第1個] - 未完成的執行緒數量:2
 [第1個] - 3--->觸發並阻塞...
 [第2個] - 未完成的執行緒數量:1
 [第2個] - 3--->觸發並阻塞...
 [第2個] - 階段--->4
 [第2個] - 註冊的執行緒數量--->3
 [第2個] - 未完成的執行緒數量:3
 [第3個] - 未完成的執行緒數量:3
 [第1個] - 未完成的執行緒數量:3
 [第2個] - 睡眠2秒
 [第1個] - 睡眠2秒
 [第3個] - 睡眠4秒
 [第2個] - 未完成的執行緒數量:3
 [第1個] - 未完成的執行緒數量:3
 [第2個] - 2--->觸發並阻塞...
 [第1個] - 2--->觸發並阻塞...
 [第3個] - 未完成的執行緒數量:1
 [第3個] - 2--->觸發並阻塞...
 [第3個] - 階段--->5
 [第3個] - 註冊的執行緒數量--->3
 [第3個] - 未完成的執行緒數量:3
 [第1個] - 未完成的執行緒數量:3
 [第2個] - 未完成的執行緒數量:3
 [第3個] - 睡眠2秒
 [第1個] - 睡眠3秒
 [第2個] - 睡眠0秒
 [第2個] - 未完成的執行緒數量:3
 [第2個] - 最後一次觸發,並登出自身
 [第2個] - 未完成的執行緒數量:2
 [第2個] - 登出完成之後註冊的執行緒數量--->2
 [第3個] - 未完成的執行緒數量:2
 [第3個] - 最後一次觸發,並登出自身
 [第3個] - 未完成的執行緒數量:1
 [第3個] - 登出完成之後註冊的執行緒數量--->1
 [第1個] - 未完成的執行緒數量:1
 [第1個] - 最後一次觸發,並登出自身
 [第1個] - 階段--->6
 [第1個] - 註冊的執行緒數量--->0
 [第1個] - 未完成的執行緒數量:0
 [第1個] - 登出完成之後註冊的執行緒數量--->0

上面程式碼中,當所有執行緒進行到arriveAndAwaitAdvance()時會觸發計數並且將執行緒阻塞,等計數數量等於註冊執行緒數量【即所有執行緒都執行到了約定的地方時,會放行,是所有執行緒得以繼續執行,並觸發onAction事件】。我們可以在onAction中根據不同階段執行不同內容的操作。

實現分階段的CountDownLatch的功能

只需將上面的測試類更改如下:

package concurrent;

import concurrent.thread.PhaserThread;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.concurrent.Phaser;

import static jodd.util.ThreadUtil.sleep;

/**
 * 拿客
 * 網站:www.coderknock.com
 * QQ群:213732117
 * 三產 建立於 2016年08月08日 21:25:30。
 */
public class PhaserTest {

    private static final Logger logger = LogManager.getLogger(PhaserTest.class);

    public static void main(String[] args) {
        //這裡其實相當於已經註冊了3個執行緒,但是並沒有實際的執行緒
        int coutNum=3;
        Phaser phaser = new Phaser(coutNum) {
            /**此方法有2個作用:
             * 1、當每一個階段執行完畢,此方法會被自動呼叫,因此,過載此方法寫入的程式碼會在每個階段執行完畢時執行,相當於CyclicBarrier的barrierAction。
             * 2、當此方法返回true時,意味著Phaser被終止,因此可以巧妙的設定此方法的返回值來終止所有執行緒。例如:若此方法返回值為 phase>=3,其含義為當整個執行緒執行了4個階段後,程式終止。
             * */
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                logger.debug("階段--->" + phase);
                logger.debug("註冊的執行緒數量--->" + registeredParties);
                return registeredParties==coutNum;//當後只剩下coutNum個執行緒時說明所有真實的註冊的執行緒已經執行完成,測試可以終止Phaser
            }
        };

        for (int i = 3; i > 0; i--) {
            new PhaserThread("第" + i + "個", phaser).start();
        }

        //當phaser未終止時迴圈註冊這塊兒可以使用實際的業務處理
        while (!phaser.isTerminated()) {
            sleep(1000);
            logger.debug("觸發一次");
            phaser.arrive(); //相當於countDownLatch.countDown();
        }
    }
}

相關文章