这是并发编程系列的第四篇文章。上一篇介绍的是通过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()
方法。
示例一
通过示例代码演示一下文章开头描述的场景,一个洗碗线程,一个烘干线程,当碗是湿的时候,则挂起,同时唤醒烘干线程,反之亦然。
定义Dish1
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
27public 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
17public class WashingTask implements Runnable {
private Dish dish;
public WashingTask(Dish dish){
this.dish = dish;
}
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
16public class DryingTask implements Runnable {
private Dish dish;
public DryingTask(Dish dish) {
this.dish = dish;
}
public void run() {
while (true){
try {
dish.drying();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试程序
1 | public static void main(String[] args){ |
该程序会交替输出如下结果1
2drying被唤醒,正在drying....
washing被唤醒,正在washing……
示例二
通过Condition
对象的awai()
和signal()
方法实现同样的功能。
通过显示锁实现的Dish
1 | public class DishLock { |
清洗任务1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class WashingTask implements Runnable {
private DishLock dishLock;
public WashingTask(DishLock dishLock) {
this.dishLock = dishLock;
}
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
16public class DryingTask implements Runnable {
private DishLock dishLock;
public DryingTask(DishLock dishLock) {
this.dishLock = dishLock;
}
public void run() {
while (true){
try {
dishLock.drying();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试程序1
2
3
4
5
6
7
8public 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日志搜索运维平台