java併發編程三劍客

java併發編程三劍客

思維導圖

CountDownLatch用法

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

構造器以及方法

構造器

只提供了一種構造器,其中參數count爲計數器

public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
主要方法
// 調用await()方法的線程會被掛起,它會等待直到count的值爲0才繼續執行 
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
// 和await()方法類似,只不過添加了一個等待時間,時間到後也會執行
public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
// 將count的值減一
 public void countDown() {
        sync.releaseShared(1);
    }

使用方法

import java.util.concurrent.CountDownLatch;

public class Test{
    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(2);

        new Thread(){
            @Override
            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(){
            @Override
            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("等待兩個子線程執行完畢...");
            latch.await();
            System.out.println("兩個子線程執行完畢");
            System.out.println("繼續執行主線程");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
  • 運行結果:

CyclicBarrier用法

​ 位於java.util.concurrent包下,字面意思迴環柵欄,通過它可以實現讓一組線程等待直到某個狀態後再全部同時執行,叫做迴環是因爲當所有等待線程都被釋放以後,CyclicBarrier可以重新使用。我們把這個狀態稱爲barrier。當調用await()方法以後,線程就處於barrier狀態

構造器以及主要方法

構造器

​ 提供了兩種構造器

// 參數parties是指讓多少個線程或者任務等待至barrier狀態
// 參數barrierAction爲當前這些線程都達到barrier狀態時,會執行的內容
public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }
 public CyclicBarrier(int parties) {
        this(parties, null);
    }
主要方法

​ await方法

// 比較常用,用來掛起當前線程,直至所有線程都達到barrier狀態再同時執行後續任務
public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
// 讓這些線程等待至一定的時間,如果還有線程沒有到達barrier狀態就直接讓達到barrier狀態的線程執行後續任務
public int await(long timeout, TimeUnit unit)
        throws InterruptedException,
               BrokenBarrierException,
               TimeoutException {
        return dowait(true, unit.toNanos(timeout));
    }

其中主要調用了dowait

private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        // 獲取鎖,顯然每次只有一個線程能獲取到對象的鎖,
        lock.lock();
        try {
            // 判斷是否處於下一代,默認g.broken=false;
            final Generation g = generation;

            if (g.broken)
                throw new BrokenBarrierException();
			// 如果線程被中斷調用breadBarrier退出屏障並拋出異常
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
			// 減少線程達到屏障線程數
            int index = --count;
            // 如果所有線程達到屏障,喚醒其他線程繼續執行
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    // 獲取需要指向的Runnable對象,如果不爲null則執行run方法
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    // 設置執行方法完成
                    ranAction = true;
                    // 通知其他線程繼續執行並重置下一代
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // 如果還有其他線程沒有達到屏障將執行下面的循環
            for (;;) {
                try {
                    // 是否是超時等待,不是超時等待立馬調用trip.await(),trip是Condition,調用await將會是線程阻塞,否則調用帶有超時時間的awaitnanos(nanos)(超時時間大於0的情況下)
                    if (!timed)
                        trip.await();
                    else if (nanos > 0L)
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                       
                        Thread.currentThread().interrupt();
                    }
                }
				// 如果設置了超時且過了超時時間,查看當前代是否被破壞,破壞拋出異常
                if (g.broken)
                    throw new BrokenBarrierException();
				// 不是當前代返回
                if (g != generation)
                    return index;
				// 設置了超時且超時時間小於0,設置當前代被破壞同時喚醒其他線程並拋出超時異常
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

使用方法

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

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("所有線程寫入完畢,繼續處理其他任務...");
        }
    }
}
  • 運行結果

Semaphore用法

​ 信號量,可以控制同時訪問的線程個數,通過acquire()獲取一個許可,如果沒有就等待,而release釋放一個許可

​ 位於java.util.concurrent包下,

構造器和主要方法

構造器
// 參數permits表示許可數目,即同時可以允許多少線程進行訪問
public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
// fair表示是否是公平的,即等待時間越久的越先獲得許可
 public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }
主要方法
  • acquire 若無許可可以獲取,則一直等待直到獲得許可
  • release 在釋放許可以前,必須先獲得許可
// 嘗試獲取一個許可,
public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
// 獲取peimits個許可
 public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }
// 釋放一個許可
 public void release() {
        sync.releaseShared(1);
    }
// 釋放permits個許可
 public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }
  • 以上四個方法都會被阻塞,如果想立即得到執行結果
//嘗試獲取一個許可,若獲取成功,則立即返回true,若獲取失敗就立即返回false 
public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }
// 嘗試獲取一個許可,若在指定的時間內獲取成功則立即返回true,否則立即返回false
 public boolean tryAcquire(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
// 嘗試獲取permits個許可,若獲取成功則立即返回true,或獲取失敗則立即返回false
 public boolean tryAcquire(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.nonfairTryAcquireShared(permits) >= 0;
    }
// 嘗試獲取permits個許可,若在指定時間內成功....
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
        throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
    }

使用方法

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();
                System.out.println("工人"+this.num+"佔用一個機器在生產...");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"釋放出機器");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
  • 結果

三種輔助類總結

  • CountDownLatch和CyclicBarrier都能夠實現線程之間的等待,只不過它們側重點不同:
    • CountDownLatch一般用於某個線程A等待若干個其他線程執行完任務之後,它才執行;
    • CyclicBarrier一般用於一組線程互相等待至某個狀態,然後這一組線程再同時執行;
    • CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。

(InterruptedException e) {
e.printStackTrace();
}
}
}


* 結果

[外鏈圖片轉存中...(img-zsJDpEfe-1586999675681)]

### 三種輔助類總結

* CountDownLatch和CyclicBarrier都能夠實現線程之間的等待,只不過它們側重點不同:
  * CountDownLatch一般用於某個線程A等待若干個其他線程執行完任務之後,它才執行;
  * CyclicBarrier一般用於一組線程互相等待至某個狀態,然後這一組線程再同時執行;
  * CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。

* Semaphore其實和鎖有點類似,它一般用於控制對某組資源的訪問權限。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章