在JDK的并发包里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。
CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作以后,再执行当前线程;比如我们在主线程需要开启2个其他线程,当其他的线程执行完毕以后我们再去执行主线程,针对这个需求我们就可以使用CountDownLatch来进行实现。CountDownLatch中count down是倒着数数的意思;CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的
任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch的await()方法的线程阻塞状态解除,继续执行。
CountDownLatch的相关方法
public CountDownLatch(int count) // 初始化一个指定计数器的CountDownLatch对象
public void await() throws InterruptedException // 让当前线程等待
public void countDown() // 计数器进行减1
案例演示:使用CountDownLatch完成上述需求(我们在主线程需要开启2个其他线程,当其他的线程执行完毕以后我们再去执行主线程)
实现思路:在main方法中创建一个CountDownLatch对象,把这个对象作为作为参数传递给其他的两个任务线程
线程任务类1
public class CountDownLatchThread01 implements Runnable {
// CountDownLatch类型成员变量
private CountDownLatch countDownLatch ;
public CountDownLatchThread01(CountDownLatch countDownLatch) { // 构造方法的作用:接收CountDownLatch对象
this.countDownLatch = countDownLatch ;
}
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println("10秒以后执行了CountDownLatchThread01......");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用CountDownLatch对象的countDown方法对计数器进行-1操作
countDownLatch.countDown();
}
}
线程任务类2
public class CountDownLatchThread02 implements Runnable {
// CountDownLatch类型成员变量
private CountDownLatch countDownLatch ;
public CountDownLatchThread02(CountDownLatch countDownLatch) { // 构造方法的作用:接收CountDownLatch对象
this.countDownLatch = countDownLatch ;
}
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println("3秒以后执行了CountDownLatchThread02......");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用CountDownLatch对象的countDown方法对计数器进行-1操作
countDownLatch.countDown();
}
}
测试类
public class CountDownLatchDemo01 {
public static void main(String[] args) {
// 1. 创建一个CountDownLatch对象
CountDownLatch countDownLatch = new CountDownLatch(2) ; // CountDownLatch中的计数器的默认值就是2
// 2. 创建线程任务类对象,并且把这个CountDownLatch对象作为构造方法的参数进行传递
CountDownLatchThread01 countDownLatchThread01 = new CountDownLatchThread01(countDownLatch) ;
// 3. 创建线程任务类对象,并且把这个CountDownLatch对象作为构造方法的参数进行传递
CountDownLatchThread02 countDownLatchThread02 = new CountDownLatchThread02(countDownLatch) ;
// 4. 创建线程对象,并启动线程
Thread t1 = new Thread(countDownLatchThread01);
Thread t2 = new Thread(countDownLatchThread02);
t1.start();
t2.start();
// 5. 在主线程中调用 CountDownLatch中的await让主线程处于阻塞状态
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 6. 程序结束的输出
System.out.println("主线程执行了.... 程序结束了......");
}
}
控制台输出结果
3秒以后执行了CountDownLatchThread02......
10秒以后执行了CountDownLatchThread01......
主线程执行了.... 程序结束了......
CountDownLatchThread02线程先执行完毕,此时计数器-1;CountDownLatchThread01线程执行完毕,此时计数器-1;当计数器的值为0的时候,主线程阻塞状态接触,主线程向下执行。
CyclicBarrier
概述以及基本使用
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障
才会开门,所有被屏障拦截的线程才会继续运行。
例如:公司召集5名员工开会,等5名员工都到了,会议开始。我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
CyclicBarrier的相关方法
public CyclicBarrier(int parties, Runnable barrierAction) // 用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景
public int await() // 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
案例演示:模拟员工开会
实现步骤:
-
创建一个员工线程类(EmployeeThread),该线程类中需要定义一个CyclicBarrier类型的形式参数
-
创建一个开会线程类(MettingThread)
-
测试类
-
创建CyclicBarrier对象
-
创建5个EmployeeThread线程对象,把第一步创建的CyclicBarrier对象作为构造方法参数传递过来
-
启动5个员工线程
-
员工线程类
public class EmployeeThread extends Thread {
// CyclicBarrier类型的成员变量
private CyclicBarrier cyclicBarrier ;
public EmployeeThread(CyclicBarrier cyclicBarrier) { // 使用构造方法对CyclicBarrier进行初始化
this.cyclicBarrier = cyclicBarrier ;
}
@Override
public void run() {
try {
// 模拟开会人员的随机到场
Thread.sleep((int) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 到了! ");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}
开会线程类
public class MettingThread extends Thread {
@Override
public void run() {
System.out.println("好了,人都到了,开始开会......");
}
}
测试类
public class CyclicBarrierDemo01 {
public static void main(String[] args) {
// 创建CyclicBarrier对象
CyclicBarrier cyclicBarrier = new CyclicBarrier(5 , new MettingThread()) ;
// 创建5个EmployeeThread线程对象,把第一步创建的CyclicBarrier对象作为构造方法参数传递过来
EmployeeThread thread1 = new EmployeeThread(cyclicBarrier) ;
EmployeeThread thread2 = new EmployeeThread(cyclicBarrier) ;
EmployeeThread thread3 = new EmployeeThread(cyclicBarrier) ;
EmployeeThread thread4 = new EmployeeThread(cyclicBarrier) ;
EmployeeThread thread5 = new EmployeeThread(cyclicBarrier) ;
// 启动5个员工线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
使用场景
使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
比如:现在存在两个文件,这个两个文件中存储的是某一个员工两年的工资信息(一年一个文件),现需要对这两个文件中的数据进行汇总;使用两个线程读取2个文件中的数据,当两个文
件中的数据都读取完毕以后,进行数据的汇总操作。
分析:要想在两个线程读取数据完毕以后进行数据的汇总,那么我们就需要定义一个任务类(该类需要实现Runnable接口);两个线程读取完数据以后再进行数据的汇总,那么我们可以将
两个线程读取到的数据先存储到一个集合中,而多线程环境下最常见的线程集合类就是ConcurrentHashMap,而这个集合需要被两个线程都可以进行使用,那么我们可以将这个集
合作为我们任务类的成员变量,然后我们在这个任务类中去定义一个CyclicBarrier对象,然后在定义一个方法(count),当调用这个count方法的时候需要去开启两个线程对象,
使用这两个线程对象读取数据,把读取到的数据存储到ConcurrentHashMap对象,当一个线程读取数据完毕以后,调用CyclicBarrier的awit方法(告诉CyclicBarrier我已经
到达了屏障),然后在任务类的run方法对ConcurrentHashMap的数据进行汇总操作;
实现步骤:
-
定义一个任务类CyclicBarrierThreadUse(实现了Runnable接口)
-
定义成员变量:CyclicBarrier ,ConcurrentHashMap
private CyclicBarrier cyclicBarrier = new CyclicBarrier(2 , this) ;
private ConcurrentHashMap<Integer , String> concurrentHashMap = new ConcurrentHashMap<Integer , String>() ;
-
定义一个方法count方法,在count方法中开启两个线程对象(可以使用匿名内部类的方式实现)
-
在run方法中对ConcurrentHashMap中的数据进行汇总
-
编写测试类CyclicBarrierThreadUseDemo
-
创建CyclicBarrierThreadUse对象,调用count方法
任务类代代码:
public class CyclicBarrierThreadUse implements Runnable {
// 当前我们两个线程到达了屏障点以后,我们需要立即对数据进行汇总, 因此我们需要使用第二个构造方法
// 并且我们当前这个类就是一个任务类,因此我们可以直接传递参数this
private CyclicBarrier cyclicBarrier = new CyclicBarrier(2 , this) ;
private ConcurrentHashMap<Integer , String> concurrentHashMap = new ConcurrentHashMap<Integer , String>() ; // 存储两个线程所读取的数据
public void count() {
// 定义一个方法count方法,在count方法中开启两个线程对象(可以使用匿名内部类的方式实现)
// 线程1
new Thread(new Runnable() {
@Override
public void run() {
// 读取数据
BufferedReader bufferedReader = null ;
try {
bufferedReader = new BufferedReader(new FileReader("D:\\salary\\2017-salary.txt")) ;
String line = null ;
while((line = bufferedReader.readLine()) != null) {
concurrentHashMap.put(Integer.parseInt(line) , "") ; // 小的问题,工资信息不能重复
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 模拟任务的执行时间
try {
TimeUnit.SECONDS.sleep(5) ;
System.out.println(Thread.currentThread().getName() + "---------------------线程读取数据完毕....");
cyclicBarrier.await() ; //通知cyclicBarrier当前线程已经到达了屏障点
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// 线程2
new Thread(new Runnable() {
@Override
public void run() {
// 读取数据
BufferedReader bufferedReader = null ;
try {
bufferedReader = new BufferedReader(new FileReader("D:\\salary\\2019-salary.txt")) ;
String line = null ;
while((line = bufferedReader.readLine()) != null) {
concurrentHashMap.put(Integer.parseInt(line) , "") ; // 小的问题,工资信息不能重复
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 模拟任务的执行时间
try {
TimeUnit.SECONDS.sleep(10) ;
System.out.println(Thread.currentThread().getName() + "---------------------线程读取数据完毕....");
cyclicBarrier.await() ; //通知cyclicBarrier当前线程已经到达了屏障点
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public void run() {
// 获取concurrentHashMap中的数据进行汇总
Enumeration<Integer> enumeration = concurrentHashMap.keys(); // 获取concurrentHashMap中所有的键
/**
* 这个Enumeration的使用和我们之前所学习过的迭代器类似
* boolean hasMoreElements(); 判断集合中是否存在下一个元素
* E nextElement(); 获取元素
*/
int result = 0 ;
while(enumeration.hasMoreElements()) {
Integer integer = enumeration.nextElement();
result += integer ;
}
// 输出
System.out.println(result);
}
}
测试类代码:
public class CyclicBarrierThreadUseDemo01 {
public static void main(String[] args) {
// 创建任务类的对象
CyclicBarrierThreadUse cyclicBarrierThreadUse = new CyclicBarrierThreadUse();
// 调用count方法进行数据汇总
cyclicBarrierThreadUse.count();
}
}
Semaphore
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
举例:现在有一个十字路口,有多辆汽车需要进经过这个十字路口,但是我们规定同时只能有两辆汽车经过。其他汽车处于等待状态,只要某一个汽车经过了这个十字路口,其他的汽车才可以经
过,但是同时只能有两个汽车经过。如何限定经过这个十字路口车辆数目呢? 我们就可以使用Semaphore。
Semaphore的常用方法
public Semaphore(int permits) permits 表示许可线程的数量
public void acquire() throws InterruptedException 表示获取许可
public void release() 表示释放许可
案例演示:模拟汽车通过十字路口
实现步骤:
-
创建一个汽车的线程任务类(CarThreadRunnable),在该类中定义一个Semaphore类型的成员变量
-
创建测试类
-
创建线程任务类对象
-
创建5个线程对象,并启动。(5个线程对象,相当于5辆汽车)
-
CarThreadRunnable类
public class CarThreadRunnable implements Runnable {
// 创建一个Semaphore对象,限制只允许2个线程获取到许可证
private Semaphore semaphore = new Semaphore(2) ;
@Override
public void run() { // 这个run只允许2个线程同时执行
try {
// 获取许可证
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "----->>正在经过十字路口");
// 模拟车辆经过十字路口所需要的时间
Random random = new Random();
int nextInt = random.nextInt(7);
TimeUnit.SECONDS.sleep(nextInt);
System.out.println(Thread.currentThread().getName() + "----->>驶出十字路口");
// 释放许可证
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类
public class SemaphoreDemo01 {
public static void main(String[] args) {
// 创建线程任务类对象
CarThreadRunnable carThreadRunnable = new CarThreadRunnable() ;
// 创建5个线程对象,并启动。
for(int x = 0 ; x < 5 ; x++) {
new Thread(carThreadRunnable).start();
}
}
}
控制台输出结果
Thread-0----->>正在经过十字路口
Thread-1----->>正在经过十字路口
Thread-1----->>驶出十字路口
Thread-2----->>正在经过十字路口
Thread-0----->>驶出十字路口
Thread-3----->>正在经过十字路口
Thread-2----->>驶出十字路口
Thread-4----->>正在经过十字路口
Thread-4----->>驶出十字路口
Thread-3----->>驶出十字路口
通过控制台输出,我们可以看到当某一个汽车"驶出"十字路口以后,就会有一个汽车立马驶入。
Exchanger
概述以及基本使用
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
举例:比如男女双方结婚的时候,需要进行交换结婚戒指。
Exchanger常用方法
public Exchanger() // 构造方法
public V exchange(V x) // 进行交换数据的方法,参数x表示本方数据 ,返回值v表示对方数据
这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,
将本线程生产出来的数据传递给对方。
案例演示:模拟交互结婚戒指
实现步骤:
-
创建一个男方的线程类(ManThread),定义一个Exchanger类型的成员变量
-
创建一个女方的线程类(WomanThread),定义一个Exchanger类型的成员变量
-
测试类
-
创建一个Exchanger对象
-
创建一个ManThread对象,把第一步所创建的Exchanger作为构造方法参数传递过来
-
创建一个WomanThread对象,把第一步所创建的Exchanger作为构造方法参数传递过来
-
启动两个线程
-
ManThread类
public class ManThread extends Thread {
// 定义Exchanger类型的变量
private Exchanger<String> exchanger ;
private String name ;
public ManThread(Exchange<String> exchanger , String name) {
super(name);
this.name = name ;
this.exchanger = exchanger ;
}
@Override
public void run() {
try {
String result = exchanger.exchange("钻戒");
System.out.println(name + "---->>把钻戒给媳妇");
System.out.println(name + "---->>得到媳妇给的" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
WomanThread类
public class WomanThread extends Thread {
// 定义Exchanger类型的变量
private Exchanger<String> exchanger ;
private String name ;
public WomanThread(Exchanger<String> exchanger , String name) {
super(name) ;
this.name = name ;
this.exchanger = exchanger ;
}
@Override
public void run() {
try {
String result = exchanger.exchange("铝戒");
System.out.println(name + "---->>把铝戒给老公");
System.out.println(name + "---->>得到老公给的" + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类
public class ExchangerDemo01 {
public static void main(String[] args) {
// 创建一个Exchanger对象
Exchanger<String> exchanger = new Exchanger<String>() ;
// 创建一个ManThread对象
ManThread manThread = new ManThread(exchanger , "杨过") ;
// 创建一个WomanThread对象
WomanThread womanThread = new WomanThread(exchanger , "小龙女") ;
// 启动线程
manThread.start();
womanThread.start();
}
}
使用场景
使用场景:可以做数据校对工作
比如: 现在存在一个文件,该文件中存储的是某一个员工一年的工资信息,现需要将这个员工的工资信息录入到系统中,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两
个文件,并对两个文件数据进行校对,看看是否录入一致,
实现步骤:
-
创建一个测试类(ExchangerUseDemo)
-
通过匿名内部类的方法创建两个线程对象
-
两个线程分别读取文件中的数据,然后将数据存储到各自的集合中
-
当每一个线程读取完数据以后,就将数据交换给对方
-
然后每个线程使用对方传递过来的数据与自己所录入的数据进行比对
ExchangerUseDemo类
public class ExchangerUseDemo {
public static void main(String[] args) {
// 1. 创建Exchanger对象
Exchanger<ArrayList<String>> exchanger = new Exchanger<ArrayList<String>>() ;
// 2. 通过匿名内部类的方法创建两个线程对象
new Thread(new Runnable() {
@Override
public void run() {
try {
// 读取文件中的数据,然后将其存储到集合中
ArrayList<String> arrayList = new ArrayList<String>() ;
BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\salary\\2017-salary.txt")) ;
String line = null ;
while((line = bufferedReader.readLine()) != null) {
arrayList.add(line) ;
}
// arrayList.add("90000") ;
// arrayList.set(0 , "90000") ;
arrayList.remove(0) ;
// 调用Exchanger中的exchange方法完成数据的交换
ArrayList<String> exchange = exchanger.exchange(arrayList);
// 先比对长度
if(arrayList.size() == exchange.size()) {
// 然后使用对方线程所传递过来的数据和自己线程所读取到的数据进行比对
for(int x = 0 ; x < arrayList.size() ; x++) {
// 本方数据
String benfangElement = arrayList.get(x);
// 对方数据
String duifangElement = exchange.get(x);
// 比对
if(!benfangElement.equals(duifangElement)) {
System.out.println("数据存在问题.....");
}
}
}else {
System.out.println("数据存在问题.....");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
// 线程2
new Thread(new Runnable() {
@Override
public void run() {
try {
// 读取文件中的数据,然后将其存储到集合中
ArrayList<String> arrayList = new ArrayList<String>() ;
BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\salary\\2017-salary.txt")) ;
String line = null ;
while((line = bufferedReader.readLine()) != null) {
arrayList.add(line) ;
}
// 调用Exchanger中的exchange方法完成数据的交换
ArrayList<String> exchange = exchanger.exchange(arrayList);
// 先比对长度
if(arrayList.size() == exchange.size()) {
// 然后使用对方线程所传递过来的数据和自己线程所读取到的数据进行比对
for(int x = 0 ; x < arrayList.size() ; x++) {
// 本方数据
String benfangElement = arrayList.get(x);
// 对方数据
String duifangElement = exchange.get(x);
// 比对
if(!benfangElement.equals(duifangElement)) {
System.out.println("数据存在问题.....");
}
}
}else {
System.out.println("数据存在问题.....");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}