第九篇:线程间通信之等待/通知机制
一、等待/通知机制的实现
使用wait()和notify()实现线程间的通信,wait使线程停止运行,而notify使停止的线程继续运行
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或者被中断为止。在调用wait()之前,线程必须获取该对象的对象级别锁,即只能在同步方法或者同步快中调用wait()方法。在执行wait()方法后,当前线程释放锁。如果在调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateExcepion,它是RunntimeException的一个子类,因此,不需要try-catch语句进行捕获异常
方法notify()也要在同步方法或者同步块中调用,即在调用前,线程也必须获取该对象的对象级别锁。如果在调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateExcepion。该方法用来通知那些可能等待该对象的对象锁的线程,如果有多个线程等待,则由线程规划器随机挑出其中一个呈wait()状态的线程,对其发出通知notify(一次notify方法只能通知一个执行wait方法的线程),并使它等待获取该对象的对象锁。需要注意的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait()状态的线程并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait()状态所在线程才可以获取该对象锁。当第一个获取该对象锁wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次调用notify语句,则即便该对象已空闲,其他wait状态等待线程由于没有收到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll,如果发出notify操作时没有处于阻塞状态中的线程,那么该命令会被忽略。
方法notifyAll()方法可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个想成优先执行,但有可能是随机执行,因为这取决于虚拟机的实现
二、等待/通知简单例子
例子一:
public class MyThread extends Thread { private Object lock; public MyThread(Object lock) { this.lock = lock; } @Override public void run() { try { synchronized (lock) { System.out.println("开始wait。。" + System.currentTimeMillis()); lock.wait(); System.out.println("结束wait。。" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public class MyThread2 extends Thread { private Object lock; public MyThread2(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("开始 notify。。" + System.currentTimeMillis()); lock.notify(); System.out.println("结束 notify。。" + System.currentTimeMillis()); } } public static void main(String[] args) { try { Object lock = new Object(); MyThread t = new MyThread(lock); t.start(); Thread.sleep(3000); MyThread2 t2 = new MyThread2(lock); t2.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行main方法结果:
开始wait。。1546948292756 开始 notify。。1546948295759 结束 notify。。1546948295759 结束wait。。1546948295759
结论:第一个线程在3s后被第二个线程唤醒
例子二:
public class ThreadB extends Thread { private MyList list; public ThreadB(MyList list) { this.list = list; } @Override public void run() { try { synchronized (list) { if (list.size() != 5) { System.out.println("wait begin " + System.currentTimeMillis()); list.wait(); System.out.println("wait end " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
public class ThreadA extends Thread { private MyList list; public ThreadA(MyList list) { this.list = list; } @Override public void run() { try { synchronized (list){ for (int i = 0; i < 10; i++) { list.add(); if (list.size() == 5){ list.notify(); System.out.println("已发出通知"); } System.out.println("添加了" + (i + 1) + "个元素"); Thread.sleep(100); } } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { MyList service = new MyList(); ThreadB b = new ThreadB(service); b.setName("b"); b.start(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); } }
运行main方法后结果:
wait begin 1546952240174 添加了1个元素 添加了2个元素 添加了3个元素 添加了4个元素 已发出通知 添加了5个元素 添加了6个元素 添加了7个元素 添加了8个元素 添加了9个元素 添加了10个元素 wait end 1546952241201
从结果看,wait end在最后输出,这也说明了notify()方法执行后并不立即释放锁
三、线程状态转变的示意图:
参考https://www.aliyun.com/jiaocheng/820348.html
补充:每个锁对象都有两个队列,就绪队列和阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度,反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
四、当interrupt方法遇见wait方法
当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常
public class Service { public void test(Object lock) { try { synchronized (lock) { System.out.println("wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println("wait end " + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { try { Object lock = new Object(); Thread1 a = new Thread1(lock); a.start(); Thread.sleep(5000); a.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class Thread1 extends Thread { private Object lock; public Thread1(Object lock) { this.lock = lock; } @Override public void run() { Service service = new Service(); service.test(lock); } }
运行main结果
wait begin 1546957612257 java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Object.wait(Object.java:502) at threadtest.xianchengjiantongxin.Service.test(Service.java:11) at threadtest.xianchengjiantongxin.Thread1.run(Thread1.java:17)
总结:1、执行完同步代码块就会释放对象的锁
2、执行同步代码块的过程中,遇到异常导致线程终止,锁与会被释放
3、在执行同步代码块过程中,执行了所属对象的wait方法,这个线程会释放对象锁,而此线程对象会进入线程等待池中,等待被唤醒。
五、wait(long)
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。也可以由其他线程唤醒,如果该锁设置了自动唤醒时间,但是在未到时间这段时间有其他线程用notify或notifyAll唤醒,则不等到达自动唤醒时间,直接唤醒。