前言
本篇文章我将解释《并发编程的艺术》一书中一个经典的实现线程间通信的案例,主要是使用wait() 和 notifyAll() 方法来实现的。
这段代码的作用是通过 wait()
和 notifyAll()
方法实现线程间的等待和通知机制。具体来说,代码中创建了两个线程,一个用于等待条件的满足,另一个用于在条件满足时通知等待的线程。通过对共享资源的加锁和释放,实现了线程间的协调和同步,确保线程按照期望的顺序执行。
样例展示
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws Exception {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 当条件不满足时,继续wait,同时释放了lock的锁
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true. wait@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
}
}
// 条件满足时,完成工作
System.out.println(Thread.currentThread() + " flag is false. running@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
public void run() {
// 加锁,拥有lock的Monitor
synchronized (lock) {
// 获取lock的锁
// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
System.out.println(Thread.currentThread() + " hold lock. notify @ " +new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again. sleep@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}
代码解析
首先主方法中定义了一个falg变量和一个object对象lock。
static boolean flag = true;
随后创建了一个Wait线程,并使其进入就绪态,然后沉睡一秒,随后创建Notify线程让其进入就绪态,因为线程沉睡了一秒,所以一定是Wait线程先从就绪态进入执行态。
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
所以下面来看一下Wait类中具体代码,Wait线程开始运行。
首先它是一个继承了Runnable接口的线程类,该线程在运行时首先对lock对象进行上锁时其他线程不能对其上锁,如果当前lock已被上锁则这里不会进入执行。
synchronized (lock)
如果进入执行后会进入循环,循环内先进行一个输出,随后让其因为lock进入等待状态并释放锁。
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true. wait@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
}
}
之后轮到Notify线程进入运行态,因为之前锁已经被释放,因此此时可以对lock重新上锁并进入执行。执行时先进行了一个输出,然后进行唤醒,lock.notifyAll();表示唤醒所有因为lock而进入等待的线程,这里就是把Wait线程唤醒.
synchronized (lock) {
// 获取lock的锁
// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
System.out.println(Thread.currentThread() + " hold lock. notify @ " +new
SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
再将flag变量设为false,沉睡5秒。这里要注意,该线程虽然程序了五秒,但该部分代码并未完全执行完成,因此在这5秒内并不会释放锁,5秒后该部分代码才是真正执行完毕并解锁。
之后的情况看时间片执行情况,如果此时时间片还未耗尽,则会接着执行该类下面的代码,对该线程重新上锁并执行,执行为一个输出并沉睡5秒。随后释放锁。
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again. sleep@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
但是如果在沉睡5秒后时间片刚好耗尽,那么下次执行时就可能会是Wait线程重新获得锁,并继续执行,这里有一点需要注意:
一个线程在进入等待状态后再重新进入运行态后会接着上次运行的位置继续执行,因此此时运行时会接着运行lock.wait();后面的代码,
System.out.println(Thread.currentThread() + " flag is false. running@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
后面该线程进行了一段输出后代码执行完毕再次释放锁。至此整体代码执行完毕。
最后执行结果如下:
因此总体而言,这段代码展示了使用 wait()
和 notifyAll()
方法进行线程间通信的基本模式,其中一个线程等待某个条件的满足,而另一个线程负责在条件满足时通知等待的线程。