Java 官方提供了一些比较实用的并发工具类,能够使我们很轻松的驾驭多线程,不用再担心线程安全问题。在工作中巧妙使用这些并发工具类,能够达到事半功倍的效果。下面我们就一起看看这些并发工具类吧。
在 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 就对了。
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 的使用场景就是:控制并发执行的线程数量。其主要方法如下:
方法 | 说明 |
---|---|
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(); } } }
就先介绍到这里吧,希望本篇博客的内容,能够对大家有用。