Java併發——這些工具類你用過嗎?

前言

J.U.C包中提供了一些非常有用的工具類。在合適的場景下使用它們往往能夠達到事半功倍的效果。比如Atomic工具類、Exchanger、CountDownLatch、CyclicBarrier、Semaphore這些。

Atomic工具類

Atomic工具類能夠實現原子操作數據。從數據類型的角度來看,可以分爲:基本數據類型、數組、引用類型、引用類型屬性的原子更新操作。它的底層原理其實就是對於Unsafe類和volatile的封裝。
像AtomicInteger、AtomicLong等內部源碼都是差不多的。值得注意的是像AtomicBoolean/AtomicChar等的實現原理其實是內部將boolean、char、byte、等數據類型向上轉型爲了int類型,本質上和AtomicInteger差別不大。

Exchanger

Exchanger可以交換線程間的數據,是一個線程間協作工作的工具類。它提供了一個同步點,用於線程間交換數據。Exchanger只能交換2個線程間的數據。理應如此:生活中,不論是交換利益、交換物品,這些行爲都是發生在兩者之間。
在實際的場景中,Exchanger十分適用於兩個任務之間有數據上的相互依賴的場景。比如交易系統中的對賬功能。我們以一個實際例子來看一看Exchanger的使用:

public class ExchangerTest {
    public static final Exchanger<Integer> transExchanger = new Exchanger<>();

    public static void main(String[] args) {
        Thread th1 = new Thread(() -> {
            // 僞代碼
            Integer transA = 1000;
            try {
                Integer transFromB = transExchanger.exchange(transA);
                System.out.println("我是系統A:我係統中統計的交易額爲:" + transA + " 我在交易系統中產生的交易額爲:" + transFromB);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread th2 = new Thread(() -> {
            // 僞代碼
            Integer transB = 1001;
            try {
                Integer transFromA = transExchanger.exchange(transB);
                System.out.println("我是交易系統:系統A統計出的交易額爲:" + transB + " 系統A實際在我這裏產生的交易額爲:" + transFromA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        th1.start();
        th2.start();
    }
}

執行結果如下:

我是交易系統:系統A統計出的交易額爲:1001 系統A實際在我這裏產生的交易額爲:1000
我是系統A:我係統中統計的交易額爲:1000 我在交易系統中產生的交易額爲:1001

CountDownLatch

CountDownLatch的作用是用於多線程間同步完成任務。值得注意的是它只能使用一次,原因在於CountDownLatch設置鎖資源之後,只提供了countDown()方法來釋放鎖資源,和await()方法來等待鎖資源釋放完畢。並沒有重置鎖資源的功能。如果我們理解AQS源碼的話,那麼讀源碼和對於CountDownLatch的使用就再簡單不過了。

public class CountDownLatchTest {

    public static final CountDownLatch countDownLatch = new CountDownLatch(2);

    public static void main(String[] args) {
        Thread th1 = new Thread(() -> {
            System.out.println("th1 run.");
            countDownLatch.countDown();
        });

        Thread th2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("th2 run.");
            countDownLatch.countDown();
        });

        Thread th3 = new Thread(() -> {
            System.out.println("th3 waiting until count down 0.");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("th3 last to run.");
        });

        th1.start();
        th2.start();
        th3.start();
    }
}

執行結果:

th1 run.
th3 waiting until count down 0.
th2 run.
th3 last to run.

由於CountDownLatch只能使用一次,因此它適用於那些一次性任務。比如一些框架的初始化。

CyclicBarrier

CyclicBarrier通常被翻譯爲回欄柵。它的功能其實是類似於CountDownLatch。不同之處在於:

  • CyclicBarrier一輪完成之後可以重複使用。
  • CyclicBarrier的構造方法還支持註冊一個額外的線程,在喚醒await中的線程之前先執行這個線程。

我們分析一個例子如下:

public class CyclicBarrierTest {

    // 回欄珊,註冊一個額外的線程
    public static final CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () -> {
        System.out.println("令牌已經爲空了,準備喚醒等待中的線程.");
    });


    public static void main(String[] args) {
        // 創建一個固定大小的線程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 4; i++) {
            executorService.submit(() -> {
                try {
                    String thName = Thread.currentThread().getName();
                    System.out.println(thName + " 執行完畢,等待通知...");
                    cyclicBarrier.await();
                    System.out.println(thName + " 收到通知.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    // ignore
                }
            });
        }

        executorService.shutdown();
    }
}

執行結果如下:

執行結果
網絡上有很多針對於CyclicBarrier和CountDownLatch的區別的文章,說法總體相近。其實這二者最大的區別就在於前者支持重置資源,而後置不支持。前者提供的功能更豐富。因此前者更加適用於複雜的業務場景。如果Java併發的基礎牢固,那麼閱讀具體的源碼也是比較簡單的!

Semaphore

Semaphore翻譯過來是信號量。它的功能主要是控制併發線程數,特別適用於流量的控制,尤其是那些比較珍貴的公共資源。比如:數據庫的連接,IO操作等。
我們舉個例子如下:

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(1);
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "獲取令牌成功");
                    Thread.sleep(100);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            thread.start();
        }
    }
}

Semaphore提供了acquire()方法來獲取令牌,使用release來釋放令牌。它也是基於AQS實現的。

總結

本篇文章並沒有過多的去解讀源碼層面。截止到本篇文章,其實不難發現,如果掌握了Java併發的基本思想和底層基礎知識後,對於應用層面的解讀閱讀大多數其實是比較簡單的。這也許就是知其所以然的好處罷!

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