前面把線程相關的生命週期、關鍵字、線程池(ThreadPool)、ThreadLocal、CAS、鎖和AQS都講完了,現在就剩下怎麼來用多線程了。而要想用好多線程,其實是可以取一些巧的,比如JUC(好多面試官喜歡問的JUC,就是現在要講的JUC)。JUC就是java.util.concurrent的首字母縮寫,它是Java併發工具包就是中提供的各種工具類的統稱,主要分爲幾大類:
1、同步器;
2、線程安全的容器;
3、阻塞隊列;
4、一些特殊的類。
他們都有各自適合應用場景。這裏是併發工具包相關類的繼承結構:
下面從同步器開始。
常用的JUC同步器有四個:
1、CountDownLatch:字面意思是倒計時鎖,如果有“倒計時”的需求,那麼CountDownLatch是最好的工具。它還有一個別稱:發令槍。可以想象一下,火箭點火發射的時候,所有設備、部門都會依次檢查確認,如果全部都確認準備好了才能開始發射,也就是等倒數到指定的數字(一般是0)的時候,就開始執行預設動作;
2、Semaphore:字面意思信號量,好比紅綠燈,或者就餐排隊時餐館發的數字序號,一次只允許若干個線程執行。這個在昨天的例子裏面也已經演示過了,而且還是通過自定義AQS來實現的(信號量可能不太好理解,我更傾向於叫它搖號器);
3、CyclicBarrier:字面意思是屏障或者柵欄,與CountDownLatch比較像,但它側重於工作本身,即指定的若干個工作都滿足考覈標準(某個屏障)之後,才能繼續進行下面的工作,且可反覆使用;
4、Exchanger:用於線程之間交換數據,更形象地說法是“交換機”,即當一個線程完成某項工作後想與另一個線程交換數據,就可以使用這個工具類。
下面來一個個地演示它們的用法。
一、CountDownLatch
CountDownLatch的功能如果用圖來表示的話,就會是這樣的:
CountDownLatch實例代碼:
/** * 發令槍 */ public class CountDownLatchTester implements Runnable { static final CountDownLatch latch = new CountDownLatch(10); @Override public void run() { // 檢查任務 try { System.out.println(Thread.currentThread().getName() + " 檢查完畢!"); } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); } } public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 10; i > 0; i--) { Thread.sleep(1000); executor.submit(new CountDownLatchTester()); System.out.println(i); } Thread.sleep(1000); // 檢查 latch.await(); System.out.println(); System.out.println("點火,發射!"); // 關閉線程池 executor.shutdown(); } }
執行CountDownLatch的效果是:
二、Semaphore
Semaphore的功能如果用圖來表示的話,就會是這樣的:
Semaphore實例代碼:
/** * 信號量(搖號器) */ public class SemaphoreTester implements Runnable { static final Semaphore semaphore = new Semaphore(3); @Override public void run() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 開始進餐"); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } semaphore.release(); } public static void main(String[] args) { ExecutorService excutor = Executors.newFixedThreadPool(15); for (int i = 0; i < 15; i++) { excutor.submit(new SemaphoreTester()); } excutor.shutdown(); } }
Semaphore執行後的效果是:
三、CyclicBarrier
CyclicBarrier的功能如果用圖來表示的話,就會是這樣的:
CyclicBarrier實例代碼:
/** * 柵欄 */ public class CyclicBarrierTester implements Runnable { private final static CyclicBarrier barrier = new CyclicBarrier(3); @Override public void run() { try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " 已達到預定位置,等待指令..."); // 只有最後一個線程執行後,所有的線程才能執行2 barrier.await(); Thread.sleep(1000); // 2 所有線程都會執行的動作 System.out.println(Thread.currentThread().getName() + " 已突破第一道封鎖線"); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 15; i++) { executor.submit(new CyclicBarrierTester()); } // 關閉線程池 executor.shutdown(); } }
CyclicBarrier執行後的效果是:
四、Exchanger
Exchanger的功能如果用圖來表示的話,就會是這樣的:
Exchanger實例代碼:
/** * 交換機 */ public class ExchangerTester implements Runnable { Exchanger<Object> exchanger = null; Object object = null; public ExchangerTester(Exchanger<Object> exchanger, Object object) { this.exchanger = exchanger; this.object = object; } @Override public void run() { try { Object previous = this.object; this.object = this.exchanger.exchange(this.object); System.out.println(Thread.currentThread().getName() + " 用對象 " + previous + " 換對象 " + this.object); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Exchanger<Object> exchanger = new Exchanger<Object>(); ExchangerTester tester1 = new ExchangerTester(exchanger, "A"); ExchangerTester tester2 = new ExchangerTester(exchanger, "B"); new Thread(tester1).start(); new Thread(tester2).start(); } }
Exchanger執行後的效果是:
把這四種同步器掌握好(包括它們的組合使用),幾乎可以解決90%以上的使用多線程的場景問題,再也不用擔心不會多線程了。