java併發庫的同步工具

CountDownLatch  同步倒數計數器

CountDownLatch是一個同步倒數計數器。CountDownLatch允許一個或多個線程等待其他線程完成操作。

CountDownLatch對象內部存有一個整數作爲計數器。調用countDown()方法就將計數器減1,當計數到達0時,則所有等待者會停止等待。計數器的操作是原子性的。

 

CountDownLatch類的常用API

構造方法

CountDownLatch(int count)  構造方法參數指定了計數的次數。

方法

void await()  使當前線程在鎖存器倒計數至0之前一直等待,除非線程被中斷。

boolean await(long timeout, TimeUnit unit)  使當前線程在鎖存器倒計數至0之前一直等待,除非線程被中斷或超出了指定的等待時間。

void countDown()  計數減1。當計數爲0,則釋放所有等待的線程。

long getCount()  返回當前計數。

String toString()  返回標識此鎖存器及其狀態的字符串。

 

用給定的計數初始化 CountDownLatch實例。每調用一次countDown()方法,計數器減1。計數器大於0 時,await()方法會阻塞其他線程繼續執行。 利用該特性,可以讓主線程等待子線程的結束。

需要注意的是,一旦CountDownLatch的計數到0,則無法再將該計數無法被重置。

一種典型的場景就是火箭發射。在火箭發射前,爲了保證萬無一失,往往還要進行各項設備、儀器的檢查。只有等所有檢查完畢後,引擎才能點火。這種場景就非常適合使用CountDownLatch。它可以使得點火線程,等待所有檢查線程全部完工後,再執行。

 

例:有三個工人在爲老闆幹活。老闆有一個習慣,當三個工人把一天的活都幹完了的時候,他就來檢查所有工人所幹的活。如下代碼設計兩個類,Worker代表工人,Boss代表老闆。

複製代碼

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class CountDownLatchDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        CountDownLatch latch = new CountDownLatch(3);    // 同步倒數計數器。

        Worker w1 = new Worker(latch, "張三");
        Worker w2 = new Worker(latch, "李四");
        Worker w3 = new Worker(latch, "王五");
        Boss boss = new Boss(latch);

        executor.execute(w3);    // 工人工作。
        executor.execute(w2);
        executor.execute(w1);
        executor.execute(boss);    // 老闆工作。

        executor.shutdown();
    }

}


class Worker implements Runnable {
    private CountDownLatch downLatch;
    private String name;

    public Worker(CountDownLatch downLatch, String name) {
        this.downLatch = downLatch;
        this.name = name;
    }

    public void run() {
        this.doWork();    // 工人工作。

        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));  // 工作時長。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(this.name + "活幹完了!");
        this.downLatch.countDown();  // 計數減1。
    }

    private void doWork() {
        System.out.println(this.name + "正在幹活!");
    }

}


class Boss implements Runnable {
    private CountDownLatch downLatch;

    public Boss(CountDownLatch downLatch) {
        this.downLatch = downLatch;
    }

    public void run() {
        System.out.println("老闆正在等所有的工人幹完活......");
        try {
            this.downLatch.await();    // 當計數不爲0時,線程永遠阻塞。爲0則繼續執行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("工人活都幹完了,老闆開始檢查了!");
    }

}

複製代碼

 

CountDownLatch類與join方法

CountDownLatch實例本質與Thread的join方法相同。但join方法僅可以支持當前線程等待一個線程的結束,若需要等待多個線程,則需要逐個線程的調用join方法,非常麻煩。CountDwonLatch可以很方便的實現一個線程等待多個線程。

 

 

 

CyclicBarrier 循環屏障

CyclicBarrier用於讓一組線程運行並互相等待,直到共同到達一個公共屏障點 (common barrier point,又被稱爲同步點),被屏障攔截的所有線程就會繼續執行。

CyclicBarrier與CountDownLatch的功能非常類似。但一個CyclicBarrier實例在釋放等待線程後可以繼續使用。讓下一批線程在屏障點等待。但CountDownLatch實例只能被使用一次。所以CyclicBarrier被稱爲循環 的 barrier。

典型的比如公司的人員利用集體郊遊,先各自從家出發到公司集合,再同時出發遊玩,在指定地點集合。CyclicBarrier表示大家彼此在某處等待,集合好後纔開始出發,分散活動後又在指定地點集合碰面。

 

CyclicBarrier類API

構造器

CyclicBarrier(int parties) 創建CyclicBarrier對象,parties 表示屏障攔截的線程數量。

CyclicBarrier(int parties, Runnable barrierAction) 創建 CyclicBarrier對象,該構造方法提供了一個Runnable 參數,在一組線程中的最後一個線程到達之後,執行Runnable中的程序,再之後釋放正在等待的線程。Runnable在屏障點上只運行一次。

方法

int await() 通知CyclicBarrier實例,當前線程已經到達屏障點,然後當前線程將被阻塞。

int await(long timeout, TimeUnit unit)  指定當前線程被阻塞的時間。

int getNumberWaiting() 返回當前在屏障處等待的線程數。

int getParties() 返回CyclicBarrier的需要攔截的線程數。

boolean isBroken() 查詢此屏障是否處於損壞狀態。

void reset() 將屏障重置爲其初始狀態。

 

 

例1:各省數據獨立,分庫存偖。爲了提高計算性能,統計時採用每個省開一個線程先計算單省結果,最後彙總。

複製代碼

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

public class Total {

    public static void main(String[] args) {
        TotalService totalService = new TotalServiceImpl();
        CyclicBarrier barrier = new CyclicBarrier(5, new TotalTask(totalService));

        // 實際系統是查出所有省編碼code的列表,然後循環,每個code生成一個線程。
        new BillTask(new BillServiceImpl(), barrier, "北京").start();
        new BillTask(new BillServiceImpl(), barrier, "上海").start();
        new BillTask(new BillServiceImpl(), barrier, "廣西").start();
        new BillTask(new BillServiceImpl(), barrier, "四川").start();
        new BillTask(new BillServiceImpl(), barrier, "黑龍江").start();
    }

}


/**
* 主任務:彙總任務
*/
class TotalTask implements Runnable {
    private TotalService totalService;

    TotalTask(TotalService totalService) {
        this.totalService = totalService;
    }

    public void run() {
        // 讀取內存中各省的數據彙總,過程略。
        totalService.count();
        System.out.println("開始全國彙總");
    }

}


/**
* 子任務:計費任務
*/
class BillTask extends Thread {
    private BillService billService;     // 計費服務
    private CyclicBarrier barrier;
    private String code;    // 代碼,按省代碼分類,各省數據庫獨立。

    BillTask(BillService billService, CyclicBarrier barrier, String code) {
        this.billService = billService;
        this.barrier = barrier;
        this.code = code;
    }

    public void run() {
        System.out.println("開始計算--" + code + "省--數據!");
        billService.bill(code);

        // 把bill方法結果存入內存,如ConcurrentHashMap,vector等,代碼略
        System.out.println(code + "省已經計算完成,並通知彙總Service!");
        try {
            // 通知barrier已經完成
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}


interface BillService {
    public void bill(String code);
}

interface TotalService {
    public void count();
}


class BillServiceImpl implements BillService{

    @Override
    public void bill(String code) {}
}

class TotalServiceImpl implements TotalService{

    @Override
    public void count(){}
}

複製代碼

 

 

 

例2:賽跑時,等待所有人都準備好時,才起跑。

複製代碼

public class CyclicBarrierTest {

    public static void main(String[] args) throws IOException, InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(3);
        ExecutorService executor = Executors.newFixedThreadPool(3);

        executor.submit(new Thread(new Runner(barrier, "1號選手")));
        executor.submit(new Thread(new Runner(barrier, "2號選手")));
        executor.submit(new Thread(new Runner(barrier, "3號選手")));

        executor.shutdown();
    }
}


class Runner implements Runnable {
    // 一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)
    private CyclicBarrier barrier;
    private String name;

    public Runner(CyclicBarrier barrier, String name) {
        super();
        this.barrier = barrier;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000 * (new Random()).nextInt(8));
            System.out.println(name + " 準備好了...");
            // barrier的await方法,在所有參與者都已經在此 barrier 上調用 await 方法之前,將一直等待。
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(name + " 起跑!");
    }

}

複製代碼

 

 

 

例3:JDK6中的示例用法:下面是一個在並行分解設計中使用 barrier 的例子。給出示意代碼結構,不可運行。

複製代碼

class Solver {
    final int N;
    final float[][] data;
    final CyclicBarrier barrier;

    class Worker implements Runnable {
        int myRow;

        public Worker(int row) {
            myRow = row;
        }

        public void run() {
            while (!done()) {
                processRow(myRow);
                try {
                    barrier.await();
                } catch (InterruptedException ex) {
                    return;
                } catch (BrokenBarrierException ex) {
                    return;
                }
            }
        }

    }


    public Solver(float[][] matrix) {
        data = matrix;
        N = matrix.length;

        barrier = new CyclicBarrier(N, new Runnable() {
            public void run() {
                mergeRows(...);
            }
        });


        for (int i = 0; i < N; ++i){
            new Thread(new Worker(i)).start();
        }
     
        waitUntilDone();
    }

}

複製代碼

在這個例子中,每個 worker 線程處理矩陣的一行,在處理完所有的行之前,該線程將一直在屏障處等待。處理完所有的行之後,將執行所提供的 Runnable 屏障操作,併合並這些行。如果合併者確定已經找到了一個解決方案,那麼 done() 將返回 true,所有的 worker 線程都將終止。

如果屏障操作在執行時不依賴於正掛起的線程,則線程組中的任何線程在獲得釋放時都能執行該操作。爲方便此操作,每次調用 await() 都將返回能到達屏障處的線程的索引。然後,可以選擇哪個線程應該執行屏障操作,例如:

if (barrier.await() == 0) {
// log the completion of this iteration
}

 

 

對於失敗的同步嘗試,CyclicBarrier 使用了一種要麼全部要麼全不 (all-or-none) 的破壞模式:如果因爲中斷、失敗或者超時等原因,導致線程過早地離開了屏障點,那麼在該屏障點等待的其他所有線程也將通過 BrokenBarrierException(如果它們幾乎同時被中斷,則用 InterruptedException)以反常的方式離開。

 

 

 

Semaphore信號量

Semaphore用於控制併發線程數。Semaphore實例可以控制當前訪問自身的線程個數。使用Semaphore可以控制同時訪問資源的線程個數。例如,實現一個文件允許的併發訪問數。

Semaphore維護了一個許可集。“許可”即線程進入臨界區的許可。一個臨界區可以有多個許可。獲取許可的線程即可進入。通過 acquire() 獲取一個許可,如果線程沒有獲取到就等待,而 release() 表示釋放一個許可。可以把Semaphore看成是一種共享鎖。Semaphore允許同一時間多個線程同時訪問臨界區。

生活的理解:Semaphore實現的功能就類似廁所有5個坑,假如有十個人要上廁所,那麼同時能有多少個人去上廁所呢?同時只能有5個人能夠佔用,當5個人中的任何一個人讓開後,其中在等待的另外5個人中又有一個可以佔用了。另外等待的5個人中可以是隨機獲得優先機會,也可以是按照先來後到的順序獲得機會,這取決於構造Semaphore對象時傳入的參數選項。

Semaphore對象也可以實現互斥鎖的功能,並且可以是由一個線程獲得了"鎖",再由另一個線程釋放"鎖",這可應用於死鎖恢復的一些場合。

在一些企業系統中,開發人員經常需要限制未處理的特定資源請求(線程/操作)數量,事實上,限制有時候能夠提高系統的吞吐量,因爲它們減少了對特定資源的爭用。儘管完全可以手動編寫限制代碼,但使用 Semaphore類可以更輕鬆地完成此任務,它將幫您執行限制。

 

常用API

public void acquire()                           // 獲取許可。

public void acquireUninterruptibly()

public boolean tryAcquire()

public boolean tryAcquire(long timeout, TimeUnit unit)

public void release()                           // 釋放許可。該方法一般調用於finally塊中。

 

例:10 個線程都在運行,可以對運行SemaphoreApp的Java進程執行jstack來驗證,只有3個線程是活躍的。在一個信號計數器釋放之前,其他7個線程都處於空閒狀態。

複製代碼

import java.util.Random; 
import java.util.concurrent.Semaphore; 

public class SemaphoreApp { 

    public static void main(String[] args) {

        // 匿名Runnable實例。定義線程運行程序。
        Runnable limitedCall = new Runnable() { 
            final Random rand = new Random(); 
            final Semaphore available = new Semaphore(3);     // 最多可以發出3個"許可"
            int count = 0; 

            public void run() { 
                int time = rand.nextInt(15); 
                int num = count++; 

                try {
                    available.acquire();    // 當前線程獲取"許可"。若沒有獲取許可,則等待於此。
                    System.out.println("Executing " + "long-running action for " + time + " seconds... #" + num); 
                    Thread.sleep(time * 1000); 
                    System.out.println("Done with #" + num + "!"); 
                } catch (InterruptedException intEx) { 
                    intEx.printStackTrace(); 
                } finally {
                    available.release();     // 當前線程釋放"許可"
                }
            } 
        }; 

        for (int i = 0; i < 10; i++) {
            new Thread(limitedCall).start(); 
        }
}

複製代碼

 

例:停車示例。停車場只有10個車位,現在有30輛車去停車。當車位滿時出來一輛車纔能有一輛車進入停車。

複製代碼

import java.util.concurrent.Semaphore;

public class Car implements Runnable {
    private final Semaphore parkingSlot;
    private int carNo;

    public Car(Semaphore parkingSlot, int carNo) {
        this.parkingSlot = parkingSlot;
        this.carNo = carNo;
    }

    public void run() {
        try {
            parkingSlot.acquire();    // 車嘗試獲取"車位"
            parking();
            sleep(300);
            leaving();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            parkingSlot.release();    // 釋放"車位"
        }
    }

    private void parking() {
        System.out.println(String.format("%d號車泊車", carNo));
    }

    private void leaving() {
        System.out.println(String.format("%d號車離開車位", carNo));
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}


// --------------------------------------------------------------------------
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class ParkingCars {
    private static final int NUMBER_OF_CARS = 30;
    private static final int NUMBER_OF_PARKING_SLOT = 10;

    public static void main(String[] args) {
        Semaphore parkingSlot = new Semaphore(NUMBER_OF_PARKING_SLOT, true);    // "車位",採用FIFO,設置true。


        ExecutorService service = Executors.newCachedThreadPool();    // 創建線程池。模擬30輛車"停車"。
        for (int carNo = 1; carNo <= NUMBER_OF_CARS; carNo++) {
            service.execute(new Car(parkingSlot, carNo));
        }

        sleep(3000);
        service.shutdown();    // 關閉線程池。

        // 輸出剩餘可以用的資源數。
        System.out.println(parkingSlot.availablePermits() + " 個停車位可以用!");
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

複製代碼

 

 

 

Exchanger 交換器

Exchanger用於實現線程間的數據交換。Exchanger提供一個同步點,在同步點上,兩個線程使用exchange方法交換彼此數據。如果第一個線程先執行exchange方法,則它會等待第二個線程執行exchange方法。當兩個線程同時到達同步點時,這兩個線程即可以交換數據。交換完畢後,各自進行以後的程序流程。當兩個線程通過Exchanger交換數據的時候,這個交換對於兩個線程來說是線程安全的。

exchange()方法將本線程的數據作爲參數,傳遞給夥伴線程,並且該方法返回夥伴線程提供的數據。

當在運行不對稱的活動時Exchanger很有用,比如當一個線程填充了buffer,另一個線程從buffer中消費數據時,這兩個線程可以用Exchanger來交換數據。

 

Exchanger<V>類的API

構造器

Exchanger() 創建一個新的 Exchanger。

方法

exchange(V x) 等待另一個線程到達此交換點(除非當前線程被中斷),然後將給定的對象傳送給該線程,並接收該線程的對象。

exchange(V x, long timeout, TimeUnit unit) 等待另一個線程到達此交換點(除非當前線程被中斷,或者超出了指定的等待時間),然後將給定的對象傳送給該線程,同時接收該線程的對象。

 

例:以下這個程序demo要做的事情就是生產者在交換前生產5個"生產者",然後再與消費者交換5個數據,然後再生產5個"交換後生產者",而消費者要在交換前消費5個"消費者",然後再與生產者交換5個數據,然後再消費5個"交換後消費者"。import java.util.ArrayList;

 

複製代碼

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Exchanger;


/**
* 兩個線程間的數據交換
*/
public class ExchangerDemo {
    private static final Exchanger<List<String>> ex = new Exchanger<List<String>>();
    private static void sleep(long millis){
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
    * 內部類,數據生成者
    */
    class DataProducer implements Runnable {
        private List<String> list = new ArrayList<String>();

        public void run() {
            System.out.println("生產者開始生產數據");
            for (int i = 1; i <= 5; i++) {
                System.out.println("生產了第" + i + "個數據,耗時1秒");
                list.add("生產者" + i);
                sleep(1000);
            }
            System.out.println("生產數據結束");
            System.out.println("開始與消費者交換數據");

            try {
                //將數據準備用於交換,並返回消費者的數據
                list = (List<String>) ex.exchange(list);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("結束與消費者交換數據");
            System.out.println("生產者與消費者交換數據後,再生產數據");
            for (int i = 6; i < 10; i++) {
                System.out.println("交換後生產了第" + i + "個數據,耗時1秒");
                list.add("交換後生產者" + i);
                sleep(1000);
            }

            System.out.println("遍歷生產者交換後的數據");
            for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
                System.out.println(iterator.next());
            }
        }

    }


    /**
    * 內部類,數據消費者
    */
    class DataConsumer implements Runnable {
        private List<String> list = new ArrayList<String>();

        public void run() {
            System.out.println("消費者開始消費數據");
            for (int i = 1; i <= 5; i++) {
                System.out.println("消費了第" + i + "個數據");
                // 消費者產生數據,後面交換的時候給生產者
                list.add("消費者" + i);
             }

            System.out.println("消費數據結束");
            System.out.println("開始與生產者交換數據");
            try {
                // 進行數據交換,返回生產者的數據
                list = (List<String>) ex.exchange(list);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("消費者與生產者交換數據後,再消費數據");
            for (int i = 6; i < 10; i++) {
                System.out.println("交換後消費了第" + i + "個數據");
                list.add("交換後消費者" + i);
                sleep(1000);
            }
            sleep(1000);
            System.out.println("開始遍歷消費者交換後的數據");

            for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
                System.out.println(iterator.next());
            }
        }
    }

    // 主方法
    public static void main(String args[]) {
        ExchangerDemo et = new ExchangerDemo();
        new Thread(et.new DataProducer()).start();
        new Thread(et.new DataConsumer()).start();
    }
}

複製代碼

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