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();
}
}
}
就先介绍到这里吧,希望本篇博客的内容,能够对大家有用。