Java 常用的並發工具類介紹


Java 官方提供了一些比較實用的並發工具類,能夠使我們很輕松的駕馭多線程,不用再擔心線程安全問題。在工作中巧妙使用這些並發工具類,能夠達到事半功倍的效果。下面我們就一起看看這些並發工具類吧。


一、Hashtable 和 ConcurrentHashMap

在 Map 類型的集合中,我們最常用的是 HashMap ,但是 HashMap 並不是線程安全的。為了確保線程安全,我們可以使用 Hashtable,但是 Hashtable 的性能效率相對比較低,主要原因是 Hashtable 是通過整表加鎖來確保線程安全。為了在確保線程安全的前提下,同時兼顧性能效率,Java 在 1.5 以后提供了新的並發工具類 ConcurrentHashMap,其使用方法跟 HashMap 一樣,非常簡單。

代碼演示:

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

public class MyHashtableDemo {
    public static void main(String[] args) throws InterruptedException {
        /*
        這里分別使用 HashMap,Hashtable,ConcurrentHashMap 進行測試
        開啟兩個線程,向同一個 Map 集合中,添加 10000 條數據(key 不存在就新增,key 存在就更新)。
        最后打印出 Map 集合中的數據條數。
        */

        //HashMap<Integer, String> hm = new HashMap<>();
        //Hashtable<Integer, String> hm = new Hashtable<>();
        ConcurrentHashMap<Integer, String> hm = new ConcurrentHashMap<>();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                hm.put(i , i + "--線程1");
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                hm.put(i , i + "--線程2");
            }
        });

        t1.start();
        t2.start();

        //休眠 2 秒鍾,確保兩個線程都能夠運行完畢。
        Thread.sleep(2000);

        /*
        從打印出的 hm 中數據條數可以發現:
        當 hm 是 HashMap 時,每次打印的條數不相同,這說明 HashMap 不是線程安全的。
        當 hm 是 Hashtable 和 ConcurrentHashMap 時,每次打印的條數都是 10000 ,符合預期。
        */
        System.out.println(hm.size());
    }
}

分別使用 HashMap,Hashtable,ConcurrentHashMap 來執行上面的代碼,我們會發現 HashMap 每次打印出的數據條數具有隨機性,這說明 HashMap 不是線程安全的。Hashtable 和 ConcurrentHashMap 每次打印出的數據條數都是 10000 ,符合預期,這說明它們是線程安全的。

有關 Hashtable 和 ConcurrentHashMap 的性能對比,以及它們的底層實現原理,網上資料也很多,限於篇幅,這里就不演示和介紹了。結論就是 ConcurrentHashMap 總體性能效率要比 Hashtable 高,大家在工作中有需要的情況下,使用 ConcurrentHashMap 就對了。


二、CountDownLatch

CoutDownLatch 的使用場景是:讓一個線程等待其它線程執行完畢后再執行。其主要方法如下:

方法 說明
public CountDownLatch(int count) 構造方法中傳遞要等待的線程數量
public void await() 讓當前線程等待
public void countDown() 要等待的目標線程執行完畢后,調用此方法

為了更形象的介紹 CoutDownLatch 的使用,我們假設一個案例場景:
一個家庭里面有 3 個孩子,媽媽做好了熱騰騰的餃子給孩子們吃,等 3 個孩子都吃完餃子后,媽媽再進行打掃衛生,收拾碗筷,洗碗擦桌子。代碼實現如下:

import java.util.concurrent.CountDownLatch;

//小孩線程
public class ChileThread extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        //每個小孩都吃 15 個餃子
        for (int i = 1; i <= 15; i++) {
            System.out.println(getName() + " 吃完了第 " + i + " 個餃子");
        }

        //吃完以后,告訴媽媽一聲(每次執行 countDown 方法,就讓其內部計數器減 1)
        countDownLatch.countDown();
    }
}

//媽媽線程
public class MotherThread extends Thread {
    private CountDownLatch countDownLatch;
    public MotherThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            //讓媽媽等待,等所有孩子吃完餃子后,自動喚醒。
            //當 countDownLatch 內部計數器變成 0 的時候,會自動喚醒這里等待的線程。
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("媽媽打掃衛生,收拾碗筷,洗碗擦桌子");
    }
}

//代碼演示 CountDownLatch 的使用
public class MyCountDownLatchDemo {
    public static void main(String[] args) {
        //由於媽媽要等待 3 個小孩吃完飯,也就是要等待 3 個線程
        //所以這里創建 CountDownLatch 對象時,構造函數傳入 3
        CountDownLatch countDownLatch = new CountDownLatch(3);

        //創建媽媽線程,傳入 countDownLatch
        MotherThread motherThread = new MotherThread(countDownLatch);
        motherThread.start();

        //創建第 1 個小孩線程,傳入 countDownLatch
        ChileThread t1 = new ChileThread(countDownLatch);
        t1.setName("小孩01");

        //創建第 2 個小孩線程,傳入 countDownLatch
        ChileThread t2 = new ChileThread(countDownLatch);
        t2.setName("小孩02");

        //創建第 3 個小孩線程,傳入 countDownLatch
        ChileThread t3 = new ChileThread(countDownLatch);
        t3.setName("小孩03");

        t1.start();
        t2.start();
        t3.start();
    }
}

/*
最后運行的結果就是:
媽媽線程剛開始會進行等待,當 3 個小孩的線程都執行完畢后,媽媽線程才會執行。
*/

三、Semaphore

Semaphore 的使用場景就是:控制並發執行的線程數量。其主要方法如下:

方法 說明
public void acquire() 獲取許可,如果沒有獲取到,則進行線程阻塞,直到獲取到為止
public void release() 釋放許可,返還給 Semaphore 中

為了更形象的介紹 Semaphore 的使用,我們假設一個案例場景:
一群女生排隊上衛生間,衛生間只有 3 個坑位,衛生間外面有個大媽進行協調管理,每次最多只有 3 個女生獲得許可進入衛生間,等衛生間里面的某個或某些女生出來后,大媽再安排相應數量的其它女生進入衛生間。代碼實現如下:

import java.util.concurrent.Semaphore;

public class MyRunnable implements Runnable {
    //衛生間管理員大媽,管理衛生間 3 個坑位
    private Semaphore semaphore = new Semaphore(3);

    @Override
    public void run() {
        try {
            //獲得進入衛生間的許可
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " 進入了衛生間");

            Thread.sleep(2000); //兩秒鍾解決完內急,速度還是比較快的

            System.out.println(Thread.currentThread().getName() + " 離開了衛生間");
            //離開衛生間后,歸還許可
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MySemaphoreDemo {
    public static void main(String[] args) {
        //創建一個衛生間,所有女生都排隊上這一個衛生間
        MyRunnable mr = new MyRunnable();

        for (int i = 0; i < 100; i++) {
            //創建出 100 個女生,排隊上衛生間
            new Thread(mr).start();
        }
    }
}

就先介紹到這里吧,希望本篇博客的內容,能夠對大家有用。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM