目的:就是让线程间具有互相发送信号通信的能力。
概述 核心:利用共享对象实现通信,这里的通信不是指传值,而是发送信号。 目的:就是让线程间具有互相发送信号通信的能力。 而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。 举个例子,一个线程B可能正在等待来自线程A的信号,这个信号告诉线程B数据已经处理好了。
线程通信 开发中不免会遇到,需要所有子线程执行完毕通知主线程处理某些逻辑的场景。 或者是 线程A 在执行到某个条件通知 线程B 执行某个操作。 在java中,比较典型的就是:等待通知机制。
等待通知机制 等待通知模式是 Java 中比较经典的线程通信方式。 两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。 这种方式,有三个参与者:
阻塞线程 wait()
唤醒线程 notify()
monitor锁
看个最简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class TestWaitNotify4 { public static void main (String[] args) { Object lock = new Object(); Thread A = new Thread(new Runnable() { @Override public void run () { synchronized (lock) { System.out.println("A 1" ); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A 2" ); System.out.println("A 3" ); } } }); Thread B = new Thread(new Runnable() { @Override public void run () { synchronized (lock) { System.out.println("B 1" ); System.out.println("B 2" ); System.out.println("B 3" ); lock.notify(); } } }); A.start(); B.start(); } }
结果:
这个例子很简单,线程A
启动后,wait 自己,等待 线程B
唤醒自己。 这里 lock
,就是一个 monitor锁,是不是奇怪,为什么需要一个 monitor锁,因为等待和唤醒必须是同一个锁,后面说。
线程通信方式 不同线程之间通过使用以下方法进行通信:
wait(); 等待,该线程等待,并放弃执行权。
notify(); 唤醒,唤醒正在等待中的其他线程。
notifyAll(); 唤醒全部,推荐用这种 以上三个方法必须是同步线程中才能使用,锁对象才能使用。 只有同步才有锁的概念。
而上面三个方法是属于 Object 的方法,理由是: 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒。 不可以对不同的锁中的线程进行唤醒。 也就是说等待
和唤醒
必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
监视器对象
监视器对象,实际使用中,不止一种,不同场景有不同使用方式。monitor锁
是利用的对象的 monitor机制实现的,简单的说,对象的对象头存在隐藏字段,用来存储线程ID、锁标识、分代信息等,jvm 可以利用这个小存储进行线程状态的存储。
synchronized 方式 对象锁,就是创建一个中间对象,用来存储线程状态。 注间,这里方法使用 synchronized 进行修饰,synchronized 底层为 monitor锁。 使用 synchronized 和 synchronized(lock),这两个是等价的,锁的都是同一个 monitor锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public Object lock = new Object();public synchronized void notifySlef () { synchronized (lock) { lock.notify(); } } public void waitSlef(){ synchronized (lock) { lock.wait(); } }
this 方式 this 锁,锁的当前线程自己,所以只会等到自己执行完成才会出这个方法。 这里就有个问题,如果 this.wait(),谁来唤醒自己,因为 this 只能是自己持有,别的线程根本不可能拿到这个锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Test1 { public static void main (String[] args) { Test2 testWait = new Test2(); Thread t1 = new Thread(testWait); t1.start(); } } class Test2 implements Runnable { public synchronized void notifySelf () { System.out.println("notify,thread: " + Thread.currentThread().getName()); this .notify(); } public synchronized void waitSelf () { try { System.out.println("wait,thread: " + Thread.currentThread().getName()); this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run () { System.out.println("current thread: " + Thread.currentThread().getName()); waitSelf(); notifySelf(); } }
结果: 只 wait 住自己,没有办法再唤醒自己,卡在 wait 这一步,程序也不会退出。
1 2 current thread: Thread-0 wait,thread: Thread-0
在线程通信中,使用 this.wait() 如果没有设置超时时间,就会一直被阻塞,因为没有线程可以拿到 this 锁。
类锁方式 这种方式也是对象没,每个类都会对就有一个 Class 对象,实际上就是锁的该类的 Class 对象。
1 2 3 4 5 6 7 8 9 public static synchronized void notifyClass () { Test.class.notify(); } public void waitClass () { synchronized (Test.class) { Test.class.wait(); } }
IllegalMonitorStateException 异常 异常代码演示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class WaitTest { public void testWait () { System.out.println("Start-----" ); try { wait(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("End-------" ); } public static void main (String[] args) { final WaitTest test = new WaitTest(); new Thread(new Runnable() { @Override public void run () { test.testWait(); } }).start(); } }
错误提示
1 2 3 Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.IllegalMonitorStateException Thread-1 at java.lang.Object.wait(Native Method)
错误的主要原因为: 违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。
大白话就是 当前线程必须持有一个当前线程的锁,才能使用 wait。 当前线程并没有持有一个锁,就来调 wait 方法,直接抛异常。 要使用 wait 必须拥有该对象的锁!!! 详细说明在 wait() 方法的JDK
注释中有详细说明。
正确写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class WaitTest { public static void main (String[] args) { final WaitTest test = new WaitTest(); new Thread(new Runnable() { @Override public void run () { test.testWait(); } }).start(); } public synchronized void testWait () { System.out.println("Start-----" ); try { wait(1000000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("End-------" ); } }
总结 wait
和 notify
一般在使用时,容易搞混的就是 IllegalMonitorStateException
异常。 主要是容易忽略需要加锁这件事,因为 wait
和 notify
是对象的自有方法,一般在使用时会想当然的就调用,而忽略了要先拿到锁的前提。