Java并发编程那些事儿(四)——线程间的协作

这是并发编程系列的第四篇文章。上一篇介绍的是通过ThreadLocal的方式实现多线程间的共享资源的访问,这篇介绍一下线程之间如何进行通信。

之前介绍的内容都是如何保证线程之间的运行互不干扰,但是有的时候,线程之间必须互相合作。比如清洗盘子完成之后,才能对盘子进行烘干操作,烘干必须在清洗之后,那么清洗线程和烘干线程如何进行沟通呢?

Java进程间的通信与访问共享变量一样,都需要借助互斥的特性来实现,在互斥的基础上,JDK为线程提供了一种自我挂起的能力。也就是说想实现进程间的通信,前提必须是在synchronized同步块或者方法中实现。

实现互斥同步机制,Java提供了两种方法。内置锁synchronized和显示锁Lock,所以线程间通信也有两种方式。

第一种方式是基于内置锁,通过Object对象提供的wait()方法以及notify()/notifyAll()方法实现。

第二种方式是基于显示锁,通过Condition对象的await()方法和signal()/signaAll()方法。

内置锁——线程间通信

wait():
如果当前线程在等待某个条件发生变化之后,才能继续执行,但是触发条件发生变化,超出了当前线程的能力。通常需要另外一个线程来改变这种条件的时候,就需要考虑使用wait()方法,将当前线程挂起,等待条件的改变。

比如想把盘子由清洗状态改为烘干状态,必须由烘干线程完成。只有烘干线程才知道烘干操作什么时候完成。

当调用wait()方法将当前挂起之后,只有在notify()或者notifyAll()方法发生时,这个任务才会被唤醒,继续执行。

wait()方法提供了两种使用形式,第一种接受一个时间参数,表示在此时间范围内执行挂起操作,时间到期后自动恢复。第二种就是不接受时间参数,那么将无限挂起,除非调用了notify()或者notifyAll()方法。

notify()/notifyAll():表示唤醒正在因调用了wait()方法而等待的线程。notify()只唤醒一个等待线程,而notifyAll()会唤醒所有因持有同一把锁而等待的任务。

wait()sleep()的区别
wait()sleep()最大的区别在于,调用wait()方法时,会释放锁资源,而sleep()方法则不会。

显示锁——线程间通信

基于显示锁实现的线程间同步机制是通过Condition对象的await()方法和signal()/signalAll()方法实现的。
await()方法的作用等同于wait()signal()/signalAll()等同于notify()/notifyAll()方法。

示例一

通过示例代码演示一下文章开头描述的场景,一个洗碗线程,一个烘干线程,当碗是湿的时候,则挂起,同时唤醒烘干线程,反之亦然。

定义Dish

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Dish {
private boolean isWashing = true;
//清洗
public synchronized void washing() throws InterruptedException {
//如果已经是清洗过的状态,则挂起
while (isWashing == true){
wait();
}
System.out.println("washing被唤醒,正在washing……");
Thread.sleep(500);
isWashing = true;
//通知其它进程,可以开始工作了。
notifyAll();
}
//烘干
public synchronized void drying() throws InterruptedException {
//如果是干的,则挂起
while (isWashing == false){
wait();
}
System.out.println("drying被唤醒,正在drying....");
Thread.sleep(500);
isWashing = false;
//通知其它线程
notifyAll();
}
}

清洗任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class WashingTask implements Runnable {
private Dish dish;
public WashingTask(Dish dish){
this.dish = dish;
}
@Override
public void run() {
//如果是湿,则挂起
while (true){
try {
dish.washing();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

烘干任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DryingTask implements Runnable {
private Dish dish;
public DryingTask(Dish dish) {
this.dish = dish;
}
@Override
public void run() {
while (true){
try {
dish.drying();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

测试程序

1
2
3
4
5
6
7
8
9
public static void main(String[] args){
Dish dish = new Dish();

Thread washer = new Thread(new WashingTask(dish));
Thread dryer = new Thread(new DryingTask(dish));

washer.start();
dryer.start();
}

该程序会交替输出如下结果

1
2
drying被唤醒,正在drying....
washing被唤醒,正在washing……

示例二

通过Condition对象的awai()signal()方法实现同样的功能。

通过显示锁实现的Dish

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class DishLock {
private boolean isWashing = true;
//显示锁
private Lock lock = new ReentrantLock();
//通过显示锁拿到Condition对象
private Condition condition = lock.newCondition();
public void washing() throws InterruptedException {
lock.lock();
try{
//如果已经是清洗过的状态,则挂起
while (isWashing == true){
condition.await();
}
System.out.println("washing被唤醒,正在washing……");
Thread.sleep(500);
isWashing = true;
//通知其它进程,可以开始工作了。
condition.signalAll();
}finally {
lock.unlock();
}
}

public void drying() throws InterruptedException {
lock.lock();
try{
//如果是干的,则挂起
while (isWashing == false){
condition.await();
}
System.out.println("drying被唤醒,正在drying....");
Thread.sleep(500);
isWashing = false;
//通知其它线程
condition.signalAll();
}finally {
lock.unlock();
}
}
}

清洗任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class WashingTask implements Runnable {
private DishLock dishLock;
public WashingTask(DishLock dishLock) {
this.dishLock = dishLock;
}
@Override
public void run() {
//如果是湿,则挂起
while (true){
try {
dishLock.washing();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

烘干任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DryingTask implements Runnable {
private DishLock dishLock;
public DryingTask(DishLock dishLock) {
this.dishLock = dishLock;
}
@Override
public void run() {
while (true){
try {
dishLock.drying();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

测试程序

1
2
3
4
5
6
7
8
public static void main(String[] args){
DishLock dishLock = new DishLock();
Thread washer = new Thread(new WashingTask(dishLock));
Thread dryer = new Thread(new DryingTask(dishLock));

washer.start();
dryer.start();
}

输出结果同上。

结束

这篇文章讲述了,线程之间如何协作,是典型的生产者消费者模型。下一篇会讲一下Java提供的三个并发编程工具类,闭锁,信号量和栅栏。


推荐阅读
1. Java并发编程那些事儿(一) ——任务与线程
2. Java8的Stream流真香,没体验过的永远不知道
3. Awk这件上古神兵你会用了吗
4. 手把手教你搭建一套ELK日志搜索运维平台

-------------本文结束-------------
坚持原创技术分享,您的支持将鼓励我继续创作!
0%