Java使用wait/notify实现线程间通信上篇
爱吃南瓜糕的北络 人气:0线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体。线程间的通信就是成为整体的必用方案之一,使用线程间进行通信后,系统之间的交互性会更强大,大大提高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 获得对象锁,才得以继续执行后续代码。
加载全部内容