首頁 > 軟體

Java中JUC包(java.util.concurrent)下的常用子類

2022-12-21 14:01:00

一、物件鎖juc.locks包

在Java中除了synchronized關鍵字可以實現物件鎖之外,java.util.concurrent中的Lock介面也可以實現物件鎖。

介紹一下這個lock鎖的簡要實現:

  • JDK1.0就有的,需要JVM藉助作業系統提供的mutex系統原語實現
  • JDK1.5之後,Java語言自己實現的互斥鎖實現,不需要藉助作業系統的monitor機制

注:使用Lock介面需要顯式的進行加鎖和解鎖操作。

我們可以使用Lock介面的實現子類ReentrantLock來進行加鎖解鎖:

ReentrantLock 可重入互斥鎖. 和 synchronized 定位類似, 都是用來實現互斥效果, 保證執行緒安全.

ReentrantLock 的用法:

  • lock()加鎖,獲取鎖失敗的執行緒進入阻塞狀態,直到其他執行緒釋放鎖,再次競爭,死等。
  • trylock(超時時間): 加鎖, 獲取鎖失敗的執行緒進入阻塞態,等待一段時間,時間過了若還未獲取到鎖恢復執行,放棄加鎖,執行其他程式碼
  • unlock()解鎖

synchronized和lock的區別:

synchronized 是Java的關鍵字, 由 JVM 實現,需要依賴作業系統提供的執行緒互斥原語(mutex),而Lock標準庫的類和介面,其中一個最常用的子類( ReentrantLock ,可重入鎖),由Java本身實現的,不需要依賴作業系統

synchronized 隱式的加鎖和解鎖,lock需要顯示進行加鎖和解鎖

synchronized 在獲取鎖失敗的執行緒時,死等;lock可以使用trylock等待一段時間之後自動放棄加鎖,執行緒恢復執行

synchronized 是非公平鎖, ReentrantLock 預設是非公平鎖. 可以通過構造方法傳入一個 true 開啟公平鎖模式.

synchronized不支援讀寫鎖,Lock子類ReentrantReadWriteLock支援讀寫鎖。

更強大的喚醒機制. synchronized 是通過 Object 的 wait / notify 實現等待-喚醒. 每次喚醒的是一個隨機等待的執行緒.ReentrantLock搭配 Condition 類實現等待-喚醒, 可以更精確控制喚醒某個指定的執行緒

小結:

一般場景synchronized足夠用了,需要用超時等待鎖,公平鎖,讀寫鎖再考慮使用juc.lock

如何選擇使用哪個鎖?

  • 鎖競爭不激烈的時候, 使用 synchronized, 效率更高, 自動釋放更方便.
  • 鎖競爭激烈的時候, 使用 ReentrantLock, 搭配 trylock 更靈活控制加鎖的行為, 而不是死等.
  • 如果需要使用公平鎖, 使用 ReentrantLock.

二、原子類

原子類內部用的是 CAS 實現,所以效能要比加鎖實現 i++ 高很多。原子類有以下幾個:

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference

以 AtomicInteger 舉例,常見方法有:

addAndGet(int delta);   i += delta;
decrementAndGet(); --i;
getAndDecrement(); i--;
incrementAndGet(); ++i;
getAndIncrement(); i++;

三、四個常用工具類

juc包下一共有四個常用工具類:

  • 號誌 - Semaphore
  • 計數器 - CountDownLatch
  • 迴圈柵欄 - CyclicBarrier
  • 兩個執行緒之間的交換器 - Exchanger

3.1 號誌 Semaphore

號誌Semaphore就是一個計數器,表示當前可用資源的個數

關於號誌Semaphore有兩個核心操作:

  • P - 申請資源操作
  • V - 釋放資源操作

Semaphore 的PV加減操作都是原子性的,再多執行緒場景下可以直接使用

public static void main(String[] args) {
        // 在構造引數傳入可用資源的個數
        // 可用資源為6個
        Semaphore semaphore = new Semaphore(6);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + "準備申請資源");
                    // P操作,每次申請兩個資源
                    semaphore.acquire(2);
                    System.out.println(Thread.currentThread().getName() + "獲取資源成功");
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "釋放資源");
                    // V操作,預設釋放一個佔有的資源
                    semaphore.release(2);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 20; i++) {
            Thread t = new Thread(runnable,String.valueOf(i + 1));
            t.start();
        }
    }

3.2 CountDownLatch

有點類似於大號的join方法

呼叫await方法的執行緒需要等待其他執行緒將計數器減為0才能繼續恢復執行。

public static void main(String[] args) throws InterruptedException {
        // 等待執行緒需要等待的執行緒數,必須等這10個子執行緒全部執行完畢再恢復執行
        CountDownLatch latch = new CountDownLatch(10);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(new Random().nextInt(1000));
                    System.out.println(Thread.currentThread().getName() + "到達終點");
                    // 計數器 - 1
                    latch.countDown();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable,"運動員" + i + 1);
            t.start();
        }
        // main執行緒就是裁判執行緒,需要等待所有運動員到底終點再恢復執行
        // 直到所有執行緒呼叫countdown方法將計數器減為0繼續執行
        latch.await();
        System.out.println("比賽結束~最終獲勝的是鵬哥,有請冠軍給大家高歌一首~");
    }

總結

至於CyclicBarrier和Exchanger在本篇就不多介紹,讀者可以自行查閱一下官方檔案進行仔細的學習~如果有問題可以私信博主,別忘了點贊收藏+關注哦!


IT145.com E-mail:sddin#qq.com