1. wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
2. 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)。
3. 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程。
4. 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程。
5. 如果调用某个对象的wait()方法,当前线程必须拥有这个对象的锁,因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
6. 调用某个对象的wait()方法,相当于让当前线程交出此对象的锁,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
7. notify()方法能够唤醒一个正在等待该对象锁的线程,当有多个线程都在等待该对象的锁的话,则只能唤醒其中一个线程,具体唤醒哪个线程由虚拟机确定。
8. nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
*** 一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。 ***
例子:
/** * @author 70KG * @Title: Test02 * @Description: test * @date 2018/7/5下午9:49 */ public class Test02 { public static Object object = new Object(); public static void main(String[] args) { // 启动两个线程 Thread thread1 = new Thread1("1号"); Thread thread2 = new Thread2("2号"); thread1.start(); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } thread2.start(); } // 线程1,处于等待状态 static class Thread1 extends Thread { Thread1(String name) { this.setName(name); } @Override public void run() { synchronized (object) { try { object.wait(); } catch (InterruptedException e) { } System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁"); } } } // 线程2调用notify static class Thread2 extends Thread { Thread2(String name) { this.setName(name); } @Override public void run() { synchronized (object) { object.notify(); System.out.println("线程" + Thread.currentThread().getName() + "调用了object.notify()"); } System.out.println("线程" + Thread.currentThread().getName() + "释放了锁"); } } }
结果都是一样的,验证了上面的内容。
线程2号调用了object.notify()
线程2号释放了锁
线程1号获取到了锁
notify和notifyAll例子:
调用wait方法必须在同步块中进行。用线程来监听快递的信息,包括里程数的变化和地点的变化。
/** * @author 70KG * @Title: Express * @Description: 快递类 * @date 2018/7/4下午10:27 */ public class Express { // 始发地 private final static String CITY = "ShangHai"; // 里程变化 private int km; // 地点变化 private String site; Express() { } Express(int km, String site) { this.km = km; this.site = site; } // 里程数变化,会唤起线程 public synchronized void changeKm() { this.km = 101; notify(); } // 地点变化会唤起线程 public synchronized void changeSite() { this.site = "BeiJing"; notify(); } // 用来监听里程数的变化 public synchronized void waitKm() { while (this.km <= 100) { try { wait(); System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程被唤醒了。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getId() + "-号监听===里程变化===的线程去做相应的事了"); } // 用来监听地点的变化 public synchronized void waitSite() { while (CITY.equals(this.site)) { try { wait(); System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程被唤醒了。。。"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getId() + "-号监听===地点变化===的线程去做相应的事了"); } }
/** * @author itachi * @Title: Test * @Description: 测试 * @date 2018/7/4下午10:40 */ public class Test { // 初始化快递 private static Express express = new Express(0, "ShangHai"); // 用来监听里程数变化的线程 static class CheckKm implements Runnable { @Override public void run() { express.waitKm(); } } // 用来监听地点变化的线程 static class CheckSite implements Runnable { @Override public void run() { express.waitSite(); } } public static void main(String[] args) throws InterruptedException{ // 启动三个线程去监听里程数的变化 for (int i = 0; i <= 2; i++) { new Thread(new CheckKm()).start(); } // 启动三个线程去监听地点的变化 for (int i = 0; i <= 2; i++) { new Thread(new CheckSite()).start(); } // 主线程睡眠一秒,异常信息抛出去 Thread.sleep(1000); // 让快递的地点发生变化 express.changeSite(); } }
结果:
可见虽然是让地点发生了变化,但却随机唤醒了一个监听里程数变化的线程,并且使用整个程序处于无限等待状态。
9-号监听===里程变化===的线程被唤醒了。。。
如果将notify换成notifyAll的话,运行结果:
三个监视地点的线程都被唤醒了,各自去做各自的事情了,未被唤醒的用来监听里程数变化的线程依然处于监听状态,因为它的里程数没有变化。
14-号监听===地点变化===的线程被唤醒了。。。 14-号监听===地点变化===的线程去做相应的事了 13-号监听===地点变化===的线程被唤醒了。。。 13-号监听===地点变化===的线程去做相应的事了 12-号监听===地点变化===的线程被唤醒了。。。 12-号监听===地点变化===的线程去做相应的事了 11-号监听===里程变化===的线程被唤醒了。。。 10-号监听===里程变化===的线程被唤醒了。。。 9-号监听===里程变化===的线程被唤醒了。。。
总结起来等待和通知的标准范式:
等待方:
1. 获取对象的锁
2. 循环里面判断条件是否满足,不满足调用wait方法继续等待
3. 条件满足的话就去执行相应的业务逻辑
通知方:
1. 获取对象的锁
2. 改变条件
3. 通知所有等待在对象上的线程