Java 17 线程的通信 wait/notify 机制
前面说了很多 线程 的基础知识点,以及同步和异步, synchronized 和 volatile 关键字的使用。这一节内容就来说说线程间如何进行通信。
线程之间有了通信, 就不再是单独的个体,可以进行线程间的协作了。
我们来看个例子,在不引入线程通信的情况下如何多个线程间进行沟通。
例子很简单,先来一个操作类, 可以保存数据, 并查看当前保存条数。一个线程保存数据, 一个线程读取数据。这里例子没有太大的意义,就是为了演示该特点。
没有线程通信机制的通信。
代码如下:
class Send {
private volatile List<String> sendList = new ArrayList<>();
public void add() {
sendList.add("18500000001");
}
public int size() {
return sendList.size();
}
}
两个线程, 一个保存数据,
class ThreadA extends Thread {
private Send send;
public Thread A(Send send) {
this.send = send;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
send.add();
System.out.println("添加了 " + i + " 个元素");
TimeUnit.SECONDS.sleep(1);
}
} catch ( Exception e) {
System.out.println(e.getMessage());
}
}
}
另外一个读取数据, 读取到一定的数据, 就开始做一些操作。这里为了方便, 直接 new 个异常,结束掉线程循环。
class ThreadB extends Thread {
private Send send;
public ThreadB(Send send) {
this.send = send;
}
@Override
public void run() {
try {
while (true) {
if (send.size() == 8) {
System.out.println("send 的发送数据已经够 8 个了。准备发送数据。");
throw new interrupt edException("为了退出 while 循环,抛个异常玩玩。");
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
完整代码如下:
运行效果:
但是这样是有问题的, 因为线程的无序随机性, 如果判断里面执行的时间过长,已经超过了需要判断的条件, 可能就会出现线程内死循环,就无法退出循环了。
线程的通信 wait/notify 机制
wait 和 notify 分别是什么呢? 根据英语直译。等待和通知。我们一起学 java 好吗?学 Java 好吗? Java 好吗?好吗?这个就叫做等待, 等待着你的应答。然后你说好的, 相当于给我了通知。告诉我,可以。
并且在 java.lang .Object 中, 从基类中就有对应的方法。 方法如下:
当然有很多的例子能够来说明等待和通知的机制。去饭店点餐,你点了之后,就要等待着厨师做饭,服务员端菜,的过程。你等待的过程就是 wait 的过程。做好餐了服务员说你的餐来喽。就是 notify 通知你可以吃饭了。
通俗的来说: wait 使线程暂停运行,而 notify 通知暂停的线程继续运行。
大概的运行图如下:
写代码之前看一下这两个方法的定义:
public final void wait() throws InterruptedException
public final native void notify()
对于 wait 方法需要注意的是,该方法必须在同步方法或者同步块中调用,否则会出现异常 java.lang.IllegalMonitor State Exception
简单的演示代码:
wait
代码如下:
public class WaitNotify02 {
public static void main(String[] args) throws InterruptedException{
WaitNotify02 waitNotify02 = new WaitNotify02();
waitNotify02.wait();
}
}
运行效果:
javac -encoding UTF-8 WaitNotify02.java && java WaitNotify02
Exception in thread "main" java.lang.IllegalMonitorStateException: current thread is not owner
at java.base/java.lang.Object.wait(Native Method)
at java.base/java.lang.Object.wait(Object.java:338)
at WaitNotify02.main(WaitNotify02.java:4)
加上同步块, 调整代码运行查看效果:
这种情况下,线程就一直在等待。一直等着 notify 的通知。当然根据上面的方法, 也有另外两个重载方法,一个是传入毫秒, 一个是传入毫秒的同时, 传入 纳秒 。这个就是不会傻傻的等, 有个超时时间。
测试代码如下:
既然已经在等待了, 就不要让 wait 等着急了。notify 快来啊!
这里写一个 wait 和 notify 的小例子。
notify
测试的 main 方法类中, 显示一个 字符串 。 然后在 notify 方法中把字符串的修改了。然后看等待方法中的响应。
创建两个线程,以及一个测试类。
线程一,主要是 wait 演示
class ThreadA extends Thread {
private WaitNotify05 waitNotify05;
public ThreadA(WaitNotify05 waitNotify05) {
this.waitNotify05 = waitNotify05;
}
@Override
public void run() {
try {
synchronized (waitNotify05) {
System.out.println(Thread.currentThread().getName() + " s= " + waitNotify05.s);
System.out.println(Thread.currentThread().getName() + " wait 开始" + System.currentTimeMillis());
waitNotify05.wait();
System.out.println(Thread.currentThread().getName() + " wait 结束" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " s= " + waitNotify05.s);
}
} catch (Exception e) {
}
}
}
线程二,主要是 notify 方法演示, 并修改字符串的值。
class ThreadB extends Thread {
private WaitNotify05 waitNotify05;
public ThreadB(WaitNotify05 waitNotify05) {
this.waitNotify05 = waitNotify05;
}
@Override
public void run() {
synchronized (waitNotify05) {
System.out.println(Thread.currentThread().getName() + " notify 开始" + System.currentTimeMillis());
waitNotify05.s = "notify()";
waitNotify05.notify();
System.out.println(Thread.currentThread().getName() + " notify 结束" + System.currentTimeMillis());
}
}
}
测试方法:
import java.util.concurrent.TimeUnit;
public class WaitNotify05 {
public String s = "wait";
public static void main(String[] args) throws InterruptedException {
WaitNotify05 waitNotify05 = new WaitNotify05();
ThreadA threadA = new ThreadA(waitNotify05);
threadA.start();
TimeUnit.SECONDS.sleep(3);
ThreadB threadB = new ThreadB(waitNotify05);
threadB.start();
}
}
完整代码和运行效果如下:
测试方法中等待了 3 秒。
还记得第一个小例子吗?我们来看看使用 wait / notify 如何实现。
更改以后的代码:
运行效果如下:
需要注意的是, 发起通知的时候, 并没有立即执行 wait 的方法,是因为 ThreadA 中的同步锁还没有释放。
并且还可以看到 wait 的一个特性,就是会理解释放锁。这个和 notify 方法不同, notify 方法, 会等待着锁执行完毕。根据上面的方法就可以看出来。 等着着 for 执行结束才开始执行。
同样还有一个方法也不释放锁, 就是 sleep() 方法。
wait 和 interrupt 方法引发的异常
这里的 wait 方法,还需要注意一个问题。就是和 interrupt() 方法共同使用的时候, 会出现 InterruptedException 异常。 这也可以认为是处理中断的一种方式。因为 stop 非安全性的而且还可能会出现错误的问题。
演示代码如下:
线程等待 3 秒以后,发起中断, 之后就出现了异常信息。
以上的方法都是一个线程的情况下,等待和通知都是一个,也就是 notify 的通知也是一个一个的通知。 如果有多个线程该怎么办呢? 可以使用 notifyAll() 方法。
这里就不演示了。 可以使用数组的方法, 多启动几个线程。查看其中的问题。
关注我。后续这些细节知识会在进行迭代。感谢您的阅读。
收藏,关注,点赞。 离过年越来越近了。 大家都能回家吗?还是就地过年了呢?提前祝大家新年快乐。