JUC常用工具類

1.JUC

JUC就是java.util .concurrent工具包的簡稱。這是一個處理線程的工具包,JDK 1.5開始出現的。
分類說明JUC包常用類有哪些

1.1 CountDownLatch

1.1.1 介紹

CountDownLatch是一個同步工具類,用來協調多個線程之間的同步,或者說起到線程之間的通信(而不是用作互斥的作用)。
CountDownLatch能夠使一個線程在等待另外一些線程完成各自工作之後,再繼續執行。使用一個計數器進行實現。計數器初始值爲線程的數量。當每一個線程完成自己任務後,計數器的值就會減一。當計數器的值爲0時,表示所有的線程都已經完成一些任務,然後在CountDownLatch上等待的線程就可以恢復執行接下來的任務。

1.1.2 舉例

比如老師檢查學生作業,有三個學生,這三個學生需要都做完作業後,老師才一起進行檢查。這裏,我們把每一個學生對應一個線程,老師對應一個主線程,當主線程要進行檢查操作時,必須等待三個線程都處理完了,纔開始主線程自己的步驟。

學生類:

/**
 * 學生
 */
public class Student implements Runnable{

    //線程內共享倒計數門閥
    private CountDownLatch downLatch;
    private String name;


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

    public void run() {
        System.out.printf("學生%s 開始做作業!\n",name);
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.printf("學生%s 作業結束!\n",name);
        //當前線程執行完後,記得調用門閥
        downLatch.countDown();

    }
}

老師類:

/**
 * 老師
 */
public class Teacher {

    public static void main(String[] args) {
        //創建倒計數(CountDown)門閂(Latch)
        CountDownLatch latch = new CountDownLatch(3);
        ExecutorService executor = Executors.newCachedThreadPool();

        executor.execute(new Student(latch,"A"));
        executor.execute(new Student(latch,"B"));
        executor.execute(new Student(latch,"C"));
        //線程池用完了記得關閉,不然資源一直佔用,比如main方法會一直在執行
        executor.shutdown();

        System.out.printf("老師%s 等待學生作業!\n",Thread.currentThread().getName());
        try {
            //等待三個線程全部執行完
            latch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.printf("老師%s 學生作業全部做完,開始檢查!\n",Thread.currentThread().getName());

    }

}

打印:
在這裏插入圖片描述

1.1.3 注意

1.各個線程中最好有異常處理,不要讓一個線程阻塞了導致請他線程也跟着阻塞

1.2 CyclicBarrier

1.2.1 介紹

CyclicBarrier字面意思是“可重複使用的柵欄”,CyclicBarrier 相比 CountDownLatch 來說,要簡單很多,其源碼沒有什麼高深的地方,它是 ReentrantLock 和 Condition 的組合使用。
在CyclicBarrier類的內部有一個計數器,每個線程在到達屏障點的時候都會調用await方法將自己阻塞,此時計數器會減1,當計數器減爲0的時候所有因調用await方法而被阻塞的線程將被喚醒。這就是實現一組線程相互等待的原理

1.2.2 舉例

例如100米賽跑,有3個運動員參加,教練需要看到3個運動員都準備好了,然後才鳴槍,三個運動員同時起跑。現在三個運動員對應三個線程,當教練(主線程)執行100米比賽時,通過查看cyclicBarrier的計數是否爲0,爲0則所有線程開始都被喚醒開始同時進行。

遠動員:

/**
 * 運動員
 */
public class Player implements Runnable{

    private CyclicBarrier cyclicBarrier;
    private String name;

    public Player(CyclicBarrier cyclicBarrier, String name) {
        this.cyclicBarrier = cyclicBarrier;
        this.name = name;
    }
    public void run() {
        try {
            //睡眠阻塞
            cyclicBarrier.await();
            System.out.printf("運動員%s 開始起跑!時間:%s\n",name,getDateTime());
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.printf("運動員%s 100米跑道終點!時間:%s\n",name,getDateTime());
    }

    public String getDateTime(){
        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(d);
    }
}

教練:

/**
 * 教練
 */
public class Trainer {
    public static void main(String[] args) {
        int threadCount = 3;
        //創建柵欄
        CyclicBarrier cyclicBarrier = new CyclicBarrier(threadCount);
        ExecutorService executor = Executors.newCachedThreadPool();

        for (int i = 1; i <= threadCount; i++) {
            executor.execute(new Player(cyclicBarrier,"A"+i));
            System.out.printf("運動員%s準備就緒,等待其他玩家!\n","A"+i);
        }
        executor.shutdown();
    }
}

打印:
在這裏插入圖片描述

1.2.3 注意

1.CountDownLatch 是一次性的,CyclicBarrier 是可循環利用的
2.CountDownLatch 參與的線程的職責是不一樣的,有的在倒計時,有的在等待倒計時結束。CyclicBarrier 參與的線程職責是一樣的。
3CountDownLatch是線程組之間的等待,即一個(或多個)線程等待N個線程完成某件事情之後再執行;而CyclicBarrier則是線程組內的等待,即每個線程相互等待,即N個線程都被攔截之後,然後依次執行。
4.CountDownLatch是減計數方式,而CyclicBarrier是加計數方式。
5.CountDownLatch計數爲0無法重置,而CyclicBarrier計數達到初始值,則可以重置。

1.3 Semaphore

1.3.1 介紹

Semaphore用於限制可以訪問某些資源(物理或邏輯的)的線程數目,他維護了一個許可證集合,有多少資源需要限制就維護多少許可證集合,假如這裏有N個資源,那就對應於N個許可證,同一時刻也只能有N個線程訪問。一個線程獲取許可證就調用acquire方法,用完了釋放資源就調用release方法。
Semaphore 類是一個計數信號量,必須由獲取它的線程釋放, 通常用於限制可以訪問某些資源(物理或邏輯的)線程數目

1.3.2 舉例

比如一個停車場有三個車位,有6輛車進入,那麼當停滿3輛車後,停車場是不能再進去停車場的,必須其中一個車出來,空出一個車位後,後面的車才能再次進入。車位就相當於Semaphore信號量,車輛可看做一個線程,程序中只允許3個線程進行執行,不管後面線程有多少,當一個線程執行完了,那麼後面線程纔能有一個開始執行。

車輛類:

public class Car implements Runnable{

    private Semaphore semaphore;
    private String name;

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

    public void run() {
        try {
            //查看是否還有車位
            semaphore.acquire();
            System.out.printf("車主%s 有車位,開車進入!\n",name);
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            System.out.printf("車主%s 離開停車場!\n",name);
            //釋放佔用的車位
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

停車場類:

/**
 * 停車場
 */
public class Park {

    public static void main(String[] args) {
        //信號量,即3個車位
        Semaphore ParkingLot = new Semaphore(3);
        ExecutorService executor = Executors.newCachedThreadPool();
        //6輛車開車進入
        for (int i = 1; i <= 6; i++) {
            executor.execute(new Car(ParkingLot,"A"+i));
        }
        executor.shutdown();
    }

}

打印:
在這裏插入圖片描述

1.3.3 注意

1.4 Exchanger

1.4.1 介紹

Exchanger併發輔助類,允許在併發任務之間交換數據。具體來說Exchanger類在兩個線程之間定義同步點。當兩個線程到達同步點時,它們交換數據結構。
Exchanger 是 JDK 1.5 開始提供的一個用於兩個工作線程之間交換數據的封裝工具類,簡單說就是一個線程在完成一定的事務後想與另一個線程交換數據,則第一個先拿出數據的線程會一直等待第二個線程,直到第二個線程拿着數據到來時才能彼此交換對應數據。
平時沒怎麼用到

1.4.2 舉例

兩個生產者線程,當調用Exchanger時,會互相傳遞數據

public class ProducerA extends Thread{

    private Exchanger<Integer> exchanger;
    private static int data = 2;

    public ProducerA(Exchanger<Integer> exchanger, String name) {
        super.setName(name);
        this.exchanger = exchanger;
    }

    @Override
    public void run() {

        System.out.println(getName()+" 交換前:" + data);
        try {
            TimeUnit.SECONDS.sleep(2);
            data = exchanger.exchange(data);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName()+" 交換後:" + data);
    }
}

public class ProducerB extends Thread{

    private Exchanger<Integer> exchanger;
    private static int data = 0;

    public ProducerB(Exchanger<Integer> exchanger, String name) {
        this.exchanger = exchanger;
        super.setName(name);
    }

    @Override
    public void run() {
        System.out.println(getName()+" 交換前:" + data);
        try {
            TimeUnit.SECONDS.sleep(1);
            data = exchanger.exchange(data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName()+" 交換後:" + data);
    }
}

public class Test {

    public static void main(String[] args) throws InterruptedException {

        Exchanger<Integer> exchanger = new Exchanger<Integer>();
        new ProducerA(exchanger,"AA").start();
        new ProducerB(exchanger,"BB").start();
        TimeUnit.SECONDS.sleep(5);

    }
}


在這裏插入圖片描述

參考

Java Exchanger 必知必會
Semaphore的工作原理及實例
semaphore/
Semaphore 相關整理
Java併發編程之CyclicBarrier詳解
CountDownLatch的理解和使用

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