Java并发协作控制之同步辅助类
文章目录
1,Latch
1.1,Latch的概念
latch,英文原意是插销,它是一个同步辅助类,用来同步执行任务中一个或多个线程。它不是用来保护临界区或者共享资源,是用来协调各个线程执行到某个地方的时候,大家都暂停以下,等其他线程都到了,大家在往下走。
Latch的主要实现类:CountDownLatch,两个主要方法
- countDown() :计数器 -1
- await():等待 latch 变成 0
1.2,CountDownLatch使用
例如:百米赛跑比赛,发令枪发出信号后选手开始跑,全部选手跑到终点后比赛结束。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int runnerCount = 10;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(runnerCount);
for (int i = 0; i < runnerCount; i++) {
new Thread(new Worker(startSignal, doneSignal)).start();
}
System.out.println("准备工作...");
System.out.println("准备工作就绪");
// startSignal-1 => startSignal = 0;
// Latch变成0后,将唤醒所有在此Latch上await的线程
startSignal.countDown();
System.out.println("比赛开始");
doneSignal.await();
System.out.println("比赛结束");
}
static class Worker implements Runnable {
// 每个线程都有一个startSignal和doneSignal
// 它们都分别指向 同一个new CountDownLatch(1)对象和new CountDownLatch(runnerCount)对象
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
// 10个线程启动后 将会在此处等待,等待startSignal变成0
startSignal.await();
doWork();
// doneSignal -1
doneSignal.countDown();
} catch (InterruptedException e) { }
}
void doWork() {
System.out.println(Thread.currentThread().getName() + ": 跑完全程");
}
}
}
执行结果:
2,Barrier
2.1 Barrier的概念
barrier是一个集合点,也是一个同步辅助类,它允许多个线程在某一个点上进行同步。具体的实现类是CyclicBarrier。
- CyclicBarrier
- 构造函数是需要同步的线程数量
- await 等待其他线程,到达预定的数量后,就放行。
2.2 Barrier的使用
假定有三行数,用三个线程分别计算每一行数的和,最终计算总和。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException {
final int[][] numbers = new int[3][];
final int[] result = new int[3];
numbers[0] = new int[]{1,2,3,4,5,6,7,8,9};
numbers[1] = new int[]{10,11,12,13,14,15};
numbers[2] = new int[]{16,17,18,19,20};
CalculateFinalResult finalResultCalculator = new CalculateFinalResult(result);
CyclicBarrier barrier = new CyclicBarrier(3, finalResultCalculator);
// 当有3个线程在barrier上await时,就自动执行回调动作:finalResultCalculator
for (int i = 0; i < 3; i++) {
CalculateEachRow rowCalculator = new CalculateEachRow(barrier, numbers, result, i);
new Thread(rowCalculator).start();
}
}
}
class CalculateEachRow implements Runnable {
final CyclicBarrier barrier;
final int[][] numbers;
final int[] result;
final int rowNumber;
public CalculateEachRow(CyclicBarrier barrier, int[][] numbers, int[] result, int rowNumber) {
this.barrier = barrier;
this.numbers = numbers;
this.result = result;
this.rowNumber = rowNumber;
}
@Override
public void run() {
for (int data : numbers[rowNumber]) {
result[rowNumber] += data;
}
try {
System.out.println(Thread.currentThread().getName() +
": 计算第 " + (rowNumber + 1) +" 行结束,结果是: " + result[rowNumber]);
/*
* 当在Barrier上await的线程数量达到预定的要求后,
* 所有的await的线程不在等待 全部解锁
* 并且 Barrier将自动执行预定的回调动作
* 本程序中回调动作是CalculateFinalResult*/
barrier.await(); // 等待,只要有3个(Barrier的构造参数) 就放行
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
class CalculateFinalResult implements Runnable {
final int[] eachRowRes;
int finalRes;
public CalculateFinalResult(int[] eachRowRes) {
this.eachRowRes = eachRowRes;
}
@Override
public void run() {
finalRes = 0;
for (int data : eachRowRes) {
finalRes += data;
}
System.out.println("最终的结果为:" + finalRes);
}
}
执行结果:
3,Phaser
3.1 Phaser的概念
phaser也是一个同步辅助类,它允许执行并发多阶段的任务,在每个阶段结束的位置对线程进行同步,当所有线程都达到这步,再进行下一步。可以多次来应用。
主要类:Phaser,在Phaser类中有一系列的arrive方法,这里主要使用arriveAndAwaitAdvance()方法。
3.2 Phaser的用法
假设举行考试,总共三道大题,每次下发一道题目,等所有学生完成后再进行下一道。
package com.antique;
import java.util.concurrent.Phaser;
public class PhaserExample {
public static void main(String[] args) throws InterruptedException {
int STU_CNT = 5;
Phaser phaser = new Phaser(STU_CNT);
for (int i = 0; i < STU_CNT; i++) {
new Thread(new Student(phaser)).start();
}
}
}
class Student implements Runnable {
private final Phaser phaser;
public Student(Phaser phaser) {
this.phaser = phaser;
}
@Override
public void run() {
try {
doTesting(1);
phaser.arriveAndAwaitAdvance(); // 停下来等待其他线程完成
doTesting(2);
phaser.arriveAndAwaitAdvance();
doTesting(3);
phaser.arriveAndAwaitAdvance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void doTesting(int i) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ": 开始答题目 " + i);
long workingTime = (long) (Math.random() * 2000);
Thread.sleep(workingTime);
System.out.println(Thread.currentThread().getName() + ": 完成了题目 " + i);
}
}
执行结果:
从运行结果中可以看出,只有当所有线程都完成了题目1时,才能开始答题目2,这就实现了在某个阶段结束的位置对线程进行同步。
4,Exchanger
4.1 Exchanger的概念
Exchanger并不是用来做线程协作控制的,它是用来在线程当中互相交换信息,两个线程之间相互发消息。前面我们学过线程之间要达到信息交流,只有通过共享变量才能够实现。
Exchanger允许在2个线程中定义同步点,当两个线程都达到同步点时,它们交换数据结构。
Exchanger里面的主要方法就是exchange(),用于线程双方互相交换数据,交换数据是双向的。
4.2 Exchanger的使用
本例通过Exchanger实现学生成绩的查询,实现简单的线程间数据的交换。
package com.antique.lock;
import java.util.Scanner;
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) throws InterruptedException {
// 定义交换的数据类型
Exchanger<String> exchanger = new Exchanger<>();
// 定义一个后台工作者线程
BackgroundWorker worker = new BackgroundWorker(exchanger);
new Thread(worker).start();
Scanner in = new Scanner(System.in);
while (true) {
System.out.println("输入要查询的学生姓名:");
String input = in.nextLine().trim();
/*
* 当两个线程都执行到同一个exchanger的exchange方法,
* 两个线程就相互交换数据,交换是双向的
* 24行的exchange和47行的exchange对应
* 对于main线程来说 它把input这个值交换给了item
* 对于后台线程来说 它把null这个值交换给了main线程 main线程觉得没有 就没有接受*/
exchanger.exchange(input); // 把用户数据传递给后台线程
String value = exchanger.exchange(null); // 拿到后台线程反馈结果
if ("exit".equals(value)) {
break;
}
System.out.println("查询结果:" + value);
}
in.close();
}
}
class BackgroundWorker implements Runnable {
final Exchanger<String> exchanger;
public BackgroundWorker(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
boolean flag = true;
while (flag) {
try {
String item = exchanger.exchange(null);
switch (item) {
case "zhangsan":
exchanger.exchange("90");
break;
case "lisi":
exchanger.exchange("80");
break;
case "wangwu":
exchanger.exchange("70");
break;
case "exit":
exchanger.exchange("exit");
flag = false;
break;
default:
exchanger.exchange("查无此人");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:
需要注意的是只有当两个线程都运行到exchange() 方法时,才会进行数据交换,并且数据交换时双向的。