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();
}
}
}
就先介紹到這里吧,希望本篇博客的內容,能夠對大家有用。