线程协作
- Thread/Executor/Fork-Join
– 线程启动,运行,结束
– 线程之前缺少协作 - synchronized 同步
– 限定只能一个线程进入关键区
– 简单粗暴,性能损失大
Lock
- Lock实现同步的效果
– 实现更复杂的临界区结构
– tryLock方法可以预判锁是否空闲
– 允许分离读写操作,多个读,一个写
– 性能更好 - ReentrantLock 类,可重入的互斥锁
- ReentrantReadWriteLock 类,可重入的读写锁
- lock和unlock函数
奶茶店的例子
public class LockExample {
private static final ReentrantLock queueLock = new ReentrantLock(); //可重入锁
private static final ReentrantReadWriteLock orderLock = new ReentrantReadWriteLock(); //可重入读写锁
/**
* 有家奶茶店,点单有时需要排队
* 假设想买奶茶的人如果看到需要排队,就决定不买
* 又假设奶茶店有老板和多名员工,记单方式比较原始,只有一个订单本
* 老板负责写新订单,员工不断地查看订单本得到信息来制作奶茶,在老板写新订单时员工不能看订单本 ,读写分离
* 多个员工可同时看订单本,在员工看时老板不能写新订单
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
buyMilkTea(); //可重入锁,锁需要手动关闭
handleOrder(); //锁需手动关闭
}
public void tryToBuyMilkTea() throws InterruptedException {
boolean flag = true;
while(flag)
{
if (queueLock.tryLock()) { //重入锁,如果有人排队,就返回false
//queueLock.lock();,tryLock()中,判断是否lock,还包含了lock的操作,
long thinkingTime = (long) (Math.random() * 500);
Thread.sleep(thinkingTime);
System.out.println(Thread.currentThread().getName() + ": 来一杯珍珠奶茶,不要珍珠");
flag = false;
queueLock.unlock();
} else {
//System.out.println(Thread.currentThread().getName() + ":" + queueLock.getQueueLength() + "人在排队");
System.out.println(Thread.currentThread().getName() + ": 再等等");
}
if(flag)
{
Thread.sleep(1000);
}
}
}
public void addOrder() throws InterruptedException {
orderLock.writeLock().lock();
long writingTime = (long) (Math.random() * 1000);
Thread.sleep(writingTime);
System.out.println("老板新加一笔订单");
orderLock.writeLock().unlock();
}
public void viewOrder() throws InterruptedException {
orderLock.readLock().lock(); //readLock,读的锁,可以多个线程共享,比如这里就可以三个员工同时查看,writeLock,写的锁,只能一个线程拥有
long readingTime = (long) (Math.random() * 500);
Thread.sleep(readingTime);
System.out.println(Thread.currentThread().getName() + ": 查看订单本");
orderLock.readLock().unlock();
}
public static void buyMilkTea() throws InterruptedException {
LockExample lockExample = new LockExample();
int STUDENTS_CNT = 10;
Thread[] students = new Thread[STUDENTS_CNT]; //线程数组
for (int i = 0; i < STUDENTS_CNT; i++) {
students[i] = new Thread(new Runnable() { //数组里每一个元素都是一个匿名线程类
@Override
public void run() {
try {
long walkingTime = (long) (Math.random() * 1000); //产生随机数
Thread.sleep(walkingTime);
lockExample.tryToBuyMilkTea();
} catch(InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
);
students[i].start();
}
for (int i = 0; i < STUDENTS_CNT; i++)
students[i].join();
}
public static void handleOrder() throws InterruptedException {
LockExample lockExample = new LockExample();
Thread boss = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.addOrder(); //添加订单
long waitingTime = (long) (Math.random() * 1000);
Thread.sleep(waitingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
boss.start();
int workerCnt = 3; //员工
Thread[] workers = new Thread[workerCnt];
for (int i = 0; i < workerCnt; i++)
{
workers[i] = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.viewOrder();
long workingTime = (long) (Math.random() * 5000);
Thread.sleep(workingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
workers[i].start();
}
}
}
//输出
Thread-1: 再等等
Thread-5: 再等等
Thread-9: 再等等
Thread-8: 来一杯珍珠奶茶,不要珍珠
Thread-6: 再等等
Thread-2: 来一杯珍珠奶茶,不要珍珠
Thread-3: 再等等
Thread-4: 再等等
Thread-7: 再等等
Thread-1: 再等等
Thread-5: 再等等
Thread-0: 来一杯珍珠奶茶,不要珍珠
Thread-9: 来一杯珍珠奶茶,不要珍珠
Thread-3: 再等等
Thread-4: 再等等
Thread-7: 再等等
Thread-6: 来一杯珍珠奶茶,不要珍珠
Thread-5: 再等等
Thread-1: 来一杯珍珠奶茶,不要珍珠
Thread-4: 再等等
Thread-7: 再等等
Thread-5: 再等等
Thread-3: 来一杯珍珠奶茶,不要珍珠
Thread-7: 再等等
Thread-5: 再等等
Thread-4: 来一杯珍珠奶茶,不要珍珠
Thread-7: 来一杯珍珠奶茶,不要珍珠
Thread-5: 来一杯珍珠奶茶,不要珍珠
老板新加一笔订单
Thread-12: 查看订单本
Thread-11: 查看订单本
Thread-13: 查看订单本
老板新加一笔订单
老板新加一笔订单
Thread-11: 查看订单本
老板新加一笔订单
Thread-11: 查看订单本
Thread-12: 查看订单本
老板新加一笔订单
Thread-12: 查看订单本
读写分离了
Semaphore
- 信号量:本质是一个计数器
- 计数器大于0,可以使用,等于0不能使用
- 可以设置多个并发量,例如现在十个访问(需要计数器满足要求,才能启动线程)
- Semaphore
– acquire 获取,信号量减一
– release 释放,信号量加一 - 比Lock更进一步,可以控制多个同时访问关键区
- 限制一个关键区,多少个线程同时访问
停车的例子,五个车位,十个车的线程类,run方法就是判断是否有空车位,然后是否停进去。一个信号量来判断车位是否为满。协作多个线程
public class SemaphoreExample {
private final Semaphore placeSemaphore = new Semaphore(5);
public boolean parking() throws InterruptedException {
if (placeSemaphore.tryAcquire()) { //这里 判断是否有空车位,如果有,车停进去,会把信号量减一
System.out.println(Thread.currentThread().getName() + ": 停车成功");
return true;
} else {
System.out.println(Thread.currentThread().getName() + ": 没有空位");
return false;
}
}
public void leaving() throws InterruptedException {
placeSemaphore.release(); //信号量加一
System.out.println(Thread.currentThread().getName() + ": 开走");
}
/**
* 现有一地下车库,共有车位5个,由10辆车需要停放,每次停放时,去申请信号量
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int tryToParkCnt = 10; //定义十辆车,十个车的线程类
SemaphoreExample semaphoreExample = new SemaphoreExample();
Thread[] parkers = new Thread[tryToParkCnt];
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
long randomTime = (long) (Math.random() * 1000);
Thread.sleep(randomTime);
if (semaphoreExample.parking()) {
long parkingTime = (long) (Math.random() * 1200);
Thread.sleep(parkingTime);
semaphoreExample.leaving(); // 离开车位
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
parkers[i].start();
}
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i].join(); //这里就是让所有车线程执行完,才结束main线程
}
}
}
Thread.join()方法,在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
Latch
- 等待锁,是一个同步辅助类
- 用来同步执行任务的一个或者多个线程
- 不是用来保护临界区或者共享资源
- 用来协调各个线程,同步 一个或者多个线程在一个地方等待,等大家都到了,再往下走(采用计数器,设置计数器的数量,等到计数器到达0,才开始启动线程里工作的代码)
- CountDownLatch
– countDown() 计数器减一
– await() 等待latch变成0,变成0,await解锁。
public class CountDownLatchExample {
/**
* 设想百米赛跑比赛 发令枪发出信号后选手开始跑,全部选手跑到终点后比赛结束
*
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int runnerCnt = 10;
CountDownLatch startSignal = new CountDownLatch(1); //启动信号
CountDownLatch doneSignal = new CountDownLatch(runnerCnt); //到终点的选手,要满十个
for (int i = 0; i < runnerCnt; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
System.out.println("准备工作...");
System.out.println("准备工作就绪");
startSignal.countDown(); // 将Latch减一,变成0,将会唤醒Latch上的await线程,解锁他们的await等待
System.out.println("比赛开始");
doneSignal.await(); // 等待所以选手到达,解锁这个await
System.out.println("比赛结束");
}
static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await(); //等startSignal为0
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {
} // return;
}
void doWork() {
System.out.println(Thread.currentThread().getName() + ": 跑完全程");
}
}
}
//输出
准备工作...
准备工作就绪
比赛开始
Thread-5: 跑完全程
Thread-6: 跑完全程
Thread-4: 跑完全程
Thread-2: 跑完全程
Thread-3: 跑完全程
Thread-0: 跑完全程
Thread-8: 跑完全程
Thread-7: 跑完全程
Thread-1: 跑完全程
Thread-9: 跑完全程
比赛结束
Barrier
- 集合点,也是一个同步辅助类
- 允许多个线程在某一点上进行同步(等待所有子线程做好任务,然后await,然后await的数量到达要求,解锁await,同时可以启动一个总线程)
- CyclicBarrier 类
– 构造函数是需要同步的线程数量
– await等待其他线程,到达数后, 就放行
public class CyclicBarrierExample {
/**
* 假定有三行数,用三个线程分别计算每一行的和,最终计算总和
* @param args
*/
public static void main(String[] args) {
final int[][] numbers = new int[3][5];
final int[] results = new int[3];
int[] row1 = new int[]{1, 2, 3, 4, 5};
int[] row2 = new int[]{6, 7, 8, 9, 10};
int[] row3 = new int[]{11, 12, 13, 14, 15};
numbers[0] = row1;
numbers[1] = row2;
numbers[2] = row3;
CalculateFinalResult finalResultCalculator = new CalculateFinalResult(results);
CyclicBarrier barrier = new CyclicBarrier(3, finalResultCalculator);
//当有3个线程在barrier上await,就执行finalResultCalculator
for(int i = 0; i < 3; i++) {
CalculateEachRow rowCalculator = new CalculateEachRow(barrier, numbers, i, results);
new Thread(rowCalculator).start();
}
}
}
class CalculateEachRow implements Runnable {
final int[][] numbers;
final int rowNumber;
final int[] res;
final CyclicBarrier barrier;
CalculateEachRow(CyclicBarrier barrier, int[][] numbers, int rowNumber, int[] res) {
this.barrier = barrier;
this.numbers = numbers;
this.rowNumber = rowNumber;
this.res = res;
}
@Override
public void run() {
int[] row = numbers[rowNumber];
int sum = 0;
for (int data : row) {
sum += data;
res[rowNumber] = sum;
}
try {
System.out.println(Thread.currentThread().getName() + ": 计算第" + (rowNumber + 1) + "行结束,结果为: " + sum);
barrier.await(); //等待!只要超过3个(Barrier的构造参数),就放行。
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
class CalculateFinalResult implements Runnable {
final int[] eachRowRes;
int finalRes;
public int getFinalResult() {
return finalRes;
}
CalculateFinalResult(int[] eachRowRes) {
this.eachRowRes = eachRowRes;
}
@Override
public void run() {
int sum = 0;
for(int data : eachRowRes) {
sum += data;
}
finalRes = sum;
System.out.println("最终结果为: " + finalRes);
}
}
//输出
Thread-0: 计算第1行结束,结果为: 15
Thread-2: 计算第3行结束,结果为: 65
Thread-1: 计算第2行结束,结果为: 40
最终结果为: 120
当Barrier上的await的线程数量到达预定的要求后,所有的await的线程不再等待,全部解锁。同时Barrier会执行前面参数上的回调动作。
Barrier非常适合做这种归并程序,每个子线程做自己的任务,等到所有子线程完成后,在启动最终合并的那个线程。
Phaser
- 允许执行并发多阶段任务,同步辅助类
- 在每一个阶段结束的位置对线程进行同步,当所有的线程都达到这步,再进行下一步
- 与 Barrier不同的是,可以多次运用
- Phaser
– arrive(),不做等待
–arriveAndAwaitAdvance(),等待
public class PhaserExample {
/**
* 假设举行考试,总共三道大题,每次下发一道题目,等所有学生完成后再进行下一道
*
* @param args
*/
public static void main(String[] args) {
int studentsCnt = 5;
Phaser phaser = new Phaser(studentsCnt);
for (int i = 0; i < studentsCnt; 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(); //等到5个线程都到了,才放行
doTesting(2);
phaser.arriveAndAwaitAdvance();
doTesting(3);
phaser.arriveAndAwaitAdvance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void doTesting(int i) throws InterruptedException {
String name = Thread.currentThread().getName();
System.out.println(name + "开始答第" + i + "题");
long thinkingTime = (long) (Math.random() * 1000);
Thread.sleep(thinkingTime);
System.out.println(name + "第" + i + "道题答题结束");
}
}
Exchanger
-
允许在并发线程中互相互换消息
-
允许在俩个线程中定义同步的点,当俩个线程都达到同步点,然后开始交换数据
-
Exchanger
– exchange(),线程双方互相交互数据
– 交换数据是双向的
public class ExchangerExample {
/**
* 本例通过Exchanger实现学生成绩查询,简单线程间数据的交换
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Exchanger<String> exchanger = new Exchanger<String>();
BackgroundWorker worker = new BackgroundWorker(exchanger);
new Thread(worker).start();
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("输入要查询的属性学生姓名:");
String input = scanner.nextLine().trim();
exchanger.exchange(input); //把用户输入传递给线程
String value = exchanger.exchange(null); //拿到线程反馈结果
if ("exit".equals(value)) {
break;
}
System.out.println("查询结果:" + value);
}
scanner.close();
}
}
class BackgroundWorker implements Runnable {
final Exchanger<String> exchanger;
BackgroundWorker(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
while (true) {
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");
return;
default:
exchanger.exchange("查无此人");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:
- 只有当俩个线程都在进入 exchange ()方法并给出对象时,才能接受其他线程返回时给出的对象。
- 每次只能两个线程交换数据,如果有多个线程,也只有两个能交换彼此的数据。其他线程在各自交换,这样怎么保证是需要正确的线程交换数据?
- 当线程A达到change()时,会等待线程B达到change()吗?然后各自交换数据,中间有一个缓冲区。交换数据的时候线程A,线程B在干嘛呢?