问题如何解决
如果想解决上述问题,就需要控制多个线程按照一定的顺序轮流执行,此时就需要让线程间进行通信,保证线程任务的协调进行。为此,Java在Object类中提供了wait()、notify()、notifyAll()等方法用于解决线程间的通信问题,因为Java中所有类都是Object类的子类或间接子类,因此任何类的实例对象都可以直接使用这些方法。接下来针对这几个方法进行简要说明,如表1所示。
表1 线程通信的常用方法
方法声明 | 功能描述 |
---|---|
void wait() | 使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()或notifyAll()方法唤醒该线程为止 |
void notify() | 唤醒此同步锁上等待的第一个调用wait()方法的线程 |
void notifyAll() | 唤醒此同步锁上调用wait()方法的所有线程 |
表1中,列出了3个与线程通信相关的方法,其中wait()方法用于使当前线程进入等待状态,notify()和notifyAll()方法用于唤醒当前处于等待状态的线程。需要注意的是,wait()、notify()和notifyAll()这三个方法的调用者都应该是同步锁对象,如果这三个方法的调用者不是同步锁对象,Java虚拟机就会抛出IllegalMonitorStateException异常。
接下来通过使用wait()和notify()方法,实现线程间的通信,如文件1所示。
文件1 Example17.java
1 import java.util.*;
2 public class Example17 {
3 public static void main(String[] args) {
4 // 定义一个集合类,模拟存储生产的商品
5 List<Object> goods = new ArrayList<>();
6 // 记录线程执行前统一的起始时间start
7 long start = System.currentTimeMillis();
8 // 创建一个生产者线程,用于生产商品并存入商品集合
9 Thread thread1 = new Thread(() -> {
10 int num = 0;
11 while (System.currentTimeMillis()-start<=100) {
12 // 使用synchronized关键字同步商品生产和消费
13 synchronized (goods) {
14 // 有商品就让生产者进入等待状态
15 if(goods.size() >0){
16 try {
17 goods.wait();
18 } catch (InterruptedException e) {
19 e.printStackTrace();
20 }
21 }else{
22 // 生产者继续生产商品
23 goods.add("商品" + ++num);
24 System.out.println("生产商品" + num);
25 }
26 }
27 }
28 }, "生产者");
29 // 创建一个消费线程,用于消费商品并将商品从集合删除
30 Thread thread2 = new Thread(() -> {
31 int num = 0;
32 while (System.currentTimeMillis()-start<=100) {
33 // 使用synchronized关键字同步商品消费和消费
34 synchronized (goods) {
35 // 商品不足就唤醒生产者进行生产
36 if(goods.size()<= 0){
37 goods.notify();
38 }else{
39 // 继续消费商品
40 goods.remove("商品" + ++num);
41 System.out.println("消费商品" + num);
42 }
43 }
44 }
45 }, "消费者");
46 // 同时启动生产者和消费者两个线程,并统一执行100毫秒的时间
47 thread1.start();
48 thread2.start();
49 }
50 }
再次运行结果如图1所示。
图1 运行结果
文件1中,在生产者和消费者线程的两个执行任务中同时使用synchronized关键字同步商品生产和消费,之后每生产出商品,便调用wait()方法将当前线程置于等待状态,等待消费者线程进行消费,当消费者线程执行任务发现没有商品时便调用notify()方法唤醒对应同步锁上等待的生成者线程,让生产者线程继续生产,从而持续达到供需有序、均衡。从图1可以看出,生产者线程和消费者线程按照先生产后消费的顺序轮流执行,不再出现供需节奏不一致的问题。
需要说明的是,Java为线程等待方法wait()提供了多个重载方法,包括无参wait()方法、有等待时间的wait(long timeout)方法和wait(long timeout, int nanos)方法。其中,带有等待时间参数的wait()方法,除了会在其他线程对象调用notify()和notifyAll()方法来唤醒当前处于等待状态的线程,还会在等待时间过后自动唤醒处于等待状态的线程。