Java使用wait/notify实现线程间通信上篇

Rhea ·
更新时间:2024-09-20
· 28 次阅读

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,使用线程间进行通信后,系统之间的交互性会更强大,大大提高CPU利用率。

等待/通知机制

(1)不使用等待/通知机制实现线程间通信

样例代码如下:

public class TestC2 { public static void main(String[] args) throws Exception { MyList myList = new MyList(); ThreadA threadA = new ThreadA(myList); threadA.setName("A"); ThreadB threadB = new ThreadB(myList); threadB.setName("B"); threadB.start(); threadA.start(); } } class MyList { volatile private List list = new ArrayList(); public void add() { list.add("NanJing"); } public int size() { return list.size(); } } class ThreadA extends Thread { private MyList myList; public ThreadA(MyList myList) { this.myList = myList; } @Override public void run() { try { for (int i=0; i<10; i++) { myList.add(); System.out.println("添加了" + (i+1) + "个元素"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadB extends Thread { private MyList myList; public ThreadB(MyList myList) { this.myList = myList; } @Override public void run() { try { while (true) { int myListSize = myList.size(); if (myListSize == 5) { System.out.println("myList长度等于5了,线程需要退出了"); throw new InterruptedException(); } } } catch (InterruptedException e) { e.printStackTrace(); } } }

执行结果:

分析:

程序运行后的效果如上图所示:

虽然 ThreadA 和 ThreadB 实现了通信,但有一个弊端就是,ThreadB 需要不停地通过 while 语句轮询机制来检测某一个条件,这样会浪费CPU资源。

如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是 “等待/通知”(wait/notify)机制。

(2)什么是等待/通知机制

等待/通知机制在生活中比比皆是,比如在就餐时就会出现

厨师和服务员之间的交互要在 “菜品传递台”上,在这期间会有几个问题:

问题一:厨师做完一道菜的时间不确定,所以厨师将菜品放到“菜品传递台”上的时间也不确定。

问题二:服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)的状态。

问题三:服务员如何能取到菜呢?这又得取决于厨师,厨师将菜放在“菜品传递台”上,其实就相当于一种通知(notfiy),这时服务员才可以拿到菜并交给就餐者。

问题四:在这个过程中出现了“等待/通知”机制。

(3)等待/通知机制的实现

方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException的一个子类,因此,不需要 try-catch 语句进行捕捉异常。

方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获取该对象的对象级别锁。如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并是它等待获取该对象的对象锁。需要说明是:在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则几遍该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify 或 notifyAll。

总结一下:

wait:使线程停止运行

notify:使停止的线程继续运行

验证1:在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException。

@Test public void test1() { try { String str = new String(""); str.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }

执行结果:

验证2:调用了wait(),线程会在调用wait()所在的代码行处停止执行,直到接到通知或被中断为止。

@Test public void test2() { String lockStr = new String(""); System.out.println("sync 上面"); try { synchronized (lockStr) { System.out.println("进入 sync"); lockStr.wait(); System.out.println("wait 下的代码"); } System.out.println("sync 下面的代码"); } catch (InterruptedException e) { e.printStackTrace(); } }

执行结果:

结果分析:

线程执行了wait()方法后,程序就停止不前,不继续向下运行了。如何使呈等待wait状态的线程继续运行呢?答案就是使用notify()方法。

@Test public void test3() { try { Object lock = new Object(); ThreadC3A threadC3A = new ThreadC3A(lock); threadC3A.start(); Thread.sleep(3000); ThreadC3B threadC3B = new ThreadC3B(lock); threadC3B.start(); } catch (Exception e) { e.printStackTrace(); } } class ThreadC3A extends Thread { private Object lock; public ThreadC3A(Object lock) { this.lock = lock; } @Override public void run() { try { synchronized (lock) { System.out.println("开始 wait,Time=[" + System.currentTimeMillis() + "]"); lock.wait(); System.out.println("结束 wait,Time=[" + System.currentTimeMillis() + "]"); } } catch (Exception e) { e.printStackTrace(); } } } class ThreadC3B extends Thread { private Object lock; public ThreadC3B(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("开始 notify,Time=[" + System.currentTimeMillis() + "]"); lock.notify(); System.out.println("结束 notify,Time=[" + System.currentTimeMillis() + "]"); } } }

执行结果:
开始 wait,Time=[1659520582642]
开始 notify,Time=[1659520585652]
结束 notify,Time=[1659520585652]
结束 wait,Time=[1659520585656]

验证3:notify方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则有线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。

public class TestC5 { @Test public void test1() { Object obj = new Object(); MyArrayList list = new MyArrayList(); ThreadC5B threadC5B = new ThreadC5B(obj, list); threadC5B.start(); ThreadC5C threadC5C = new ThreadC5C(obj, list); threadC5C.start(); ThreadC5A threadC5A = new ThreadC5A(obj, list); threadC5A.start(); while (Thread.activeCount() > 1) { } } } class ThreadC5A extends Thread { private Object lock; private MyArrayList list; public ThreadC5A(Object lock, MyArrayList list) { this.lock = lock; this.list = list; } @Override public void run() { try { synchronized (lock) { for (int i=0; i<10; i++) { list.add(); System.out.println("ThreadC5A 新增第[" + (i+1) + "]个元素"); if (list.size() == 5) { lock.notify(); System.out.println("ThreadC5A 发出通知,通知等待的线程 ThreadC5B 或 ThreadC5C"); } Thread.sleep(1000); } } } catch (Exception e) { e.printStackTrace(); } } } class ThreadC5B extends Thread { private Object lock; private MyArrayList list; public ThreadC5B(Object lock, MyArrayList list) { this.lock = lock; this.list = list; } @Override public void run() { try { while (true) { synchronized (lock) { System.out.println("ThreadC5B 等待被通知"); lock.wait(); System.out.println("ThreadC5B 收到通知,退出"); return; } } } catch (Exception e) { e.printStackTrace(); } } } class ThreadC5C extends Thread { private Object lock; private MyArrayList list; public ThreadC5C(Object lock, MyArrayList list) { this.lock = lock; this.list = list; } @Override public void run() { try { while (true) { synchronized (lock) { System.out.println("ThreadC5C 等待被通知"); lock.wait(); System.out.println("ThreadC5C 收到通知,退出"); return; } } } catch (Exception e) { e.printStackTrace(); } } }

执行结果:

结果分析:

可以看到处于wait状态的ThreadC5B线程被通知到继续执行,而ThreadC5C线程则一直将处于wait状态,无法继续执行。倘若我们将ThreadC5A线程中的 lock.notify() 改写为 lock.notifyAll(),则结果就不一样,如图所示:

发现ThreadC5B 和 ThreadC5C线程均获取到了对象锁,完成了wait()后面代码的执行。

验证4:上图的结果还可以验证在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。

结果分析:

可以看出 ThreadC5A 线程在list 长度为5的时候,则通知了 ThreadC5B 和 ThreadC5C 两个处于wait状态的线程,但是 ThreadC5B 和 ThreadC5C 线程并没有立马继续执行,因为此时 ThreadC5A 并没有释放对象锁,而是继续执行 synchronized的代码块,直到退出synchronized代码块后,ThreadC5A 才释放了对象锁,ThreadC5B 和 ThreadC5C 获得对象锁,才得以继续执行后续代码。

到此这篇关于Java使用wait/notify实现线程间通信上篇的文章就介绍到这了,更多相关Java wait/notify内容请搜索软件开发网以前的文章或继续浏览下面的相关文章希望大家以后多多支持软件开发网!



notify JAVA wait 通信 线程

需要 登录 后方可回复, 如果你还没有账号请 注册新账号