Java多线程8:wait()和notify()/notifyAll()
一、轮询
线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作。
想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处理?一个办法就是,B线程while(i == 10000),这样两个线程之间就有了通信,B线程不断通过轮训来检测i == 10000这个条件。
这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操作。如果可以把这些轮询的时间释放出来,给别的线程用,就好了。
二、wait()/notify()/notifyAll()
在Object对象中有三个方法wait()、notify()、notifyAll(),既然是Object中的方法,那每个对象自然都是有的。
wait()方法:
/** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object. * In other words, this method behaves exactly as if it simply * performs the call {@code wait(0)}. * <p> * The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until another thread * notifies threads waiting on this object's monitor to wake up * either through a call to the {@code notify} method or the * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. * <p> * As in the one argument version, interrupts and spurious wakeups are * possible, and this method should always be used in a loop: * <pre> * synchronized (obj) { * while (<condition does not hold>) * obj.wait(); * ... // Perform action appropriate to condition * } * </pre> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of the object's monitor. * @throws InterruptedException if any thread interrupted the * current thread before or while the current thread * was waiting for a notification. The <i>interrupted * status</i> of the current thread is cleared when * this exception is thrown. * @see java.lang.Object#notify() * @see java.lang.Object#notifyAll() */ public final void wait() throws InterruptedException { wait(0); }
wait()使用条件:使用该方法的线程必须拥有调用wait方法的对象的对象锁。
wait()作用结果:使当前线程不在拥有该对象锁,进入等待状态,wait()处的代码停止执行,直到拥有该对象锁的另一个线程调用notify()/notifyAll()方法,唤醒使用wait()方法的线程,重新执行。
notify()方法:
/** * Wakes up a single thread that is waiting on this object's * monitor. If any threads are waiting on this object, one of them * is chosen to be awakened. The choice is arbitrary and occurs at * the discretion of the implementation. A thread waits on an object's * monitor by calling one of the {@code wait} methods. * <p> * The awakened thread will not be able to proceed until the current * thread relinquishes the lock on this object. The awakened thread will * compete in the usual manner with any other threads that might be * actively competing to synchronize on this object; for example, the * awakened thread enjoys no reliable privilege or disadvantage in being * the next thread to lock this object. * <p> * This method should only be called by a thread that is the owner * of this object's monitor. A thread becomes the owner of the * object's monitor in one of three ways: * <ul> * <li>By executing a synchronized instance method of that object. * <li>By executing the body of a {@code synchronized} statement * that synchronizes on the object. * <li>For objects of type {@code Class,} by executing a * synchronized static method of that class. * </ul> * <p> * Only one thread at a time can own an object's monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of this object's monitor. * @see java.lang.Object#notifyAll() * @see java.lang.Object#wait() */ public final native void notify();
notify()使用条件:使用该方法的线程必须拥有调用notify()方法的对象的对象锁。
notify()作用结果:唤醒一个等待着获取对象锁的线程,如果有多个线程等待着获取对象锁,将会随机选择一个。被唤醒的线程也不会立即开始执行,首先要等当前线程释放对象锁,接着被唤醒的线程和其他等待获取对象锁的线程一起排队,等着被调用。
总结起来就是,wait()使线程停止运行,notify()使停止运行的线程继续运行。
举例:wait()和notify()使用示例
线程thread01中调用wait()方法
public class Thread01 extends Thread{ private Object object; public Thread01(Object object) { this.object = object; } @Override public void run() { synchronized(object){ try { System.out.println(Thread.currentThread().getName() + " wait startTime = " + System.currentTimeMillis()); object.wait(); System.out.println(Thread.currentThread().getName() + " wait endTime = " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程thead02中调用notify()方法
public class Thread02 extends Thread{ private Object object; public Thread02(Object object) { this.object = object; } @Override public void run() { synchronized(object){ System.out.println(Thread.currentThread().getName() + "notify startTime = " + System.currentTimeMillis()); object.notify(); System.out.println(Thread.currentThread().getName() + "notify endTime = " + System.currentTimeMillis()); } } }
测试,让thread01中的wait()方法先执行
public class Test { public static void main(String[] args) throws InterruptedException { Object object = new Object(); Thread01 thread01 = new Thread01(object); Thread02 thread02 = new Thread02(object); thread01.start(); Thread.sleep(5000); thread02.start(); } }
结果:
Thread-0 wait startTime = 1553582833025 Thread-1 notify startTime = 1553582838025 Thread-1 notify endTime = 1553582838025 Thread-0 wait endTime = 1553582838025
说明:可以看到,wait的时间相差5s,也就是说,thread01调用wait()方法后,thread01停止执行,直到thread02调用notify()方法,thread01才开始继续执行。
notifyAll()方法:唤醒所有等待获取该对象锁的线程
/** * Wakes up all threads that are waiting on this object's monitor. A * thread waits on an object's monitor by calling one of the * {@code wait} methods. * <p> * The awakened threads will not be able to proceed until the current * thread relinquishes the lock on this object. The awakened threads * will compete in the usual manner with any other threads that might * be actively competing to synchronize on this object; for example, * the awakened threads enjoy no reliable privilege or disadvantage in * being the next thread to lock this object. * <p> * This method should only be called by a thread that is the owner * of this object's monitor. See the {@code notify} method for a * description of the ways in which a thread can become the owner of * a monitor. * * @throws IllegalMonitorStateException if the current thread is not * the owner of this object's monitor. * @see java.lang.Object#notify() * @see java.lang.Object#wait() */ public final native void notifyAll();
举例:在上述基础上增加thread03,与thread01一样调用wait()方法,再将thread02中的notify()方法换成notifyAll()方法
public class Thread03 extends Thread{ private Object object; public Thread03(Object object) { this.object = object; } @Override public void run() { synchronized(object){ try { System.out.println(Thread.currentThread().getName() + " wait startTime = " + System.currentTimeMillis()); object.wait(); System.out.println(Thread.currentThread().getName() + " wait endTime = " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Thread02 extends Thread{ private Object object; public Thread02(Object object) { this.object = object; } @Override public void run() { synchronized(object){ System.out.println(Thread.currentThread().getName() + " notify startTime = " + System.currentTimeMillis()); object.notifyAll(); System.out.println(Thread.currentThread().getName() + " notify endTime = " + System.currentTimeMillis()); } } }
其余都不变,测试一下:
Thread-0 wait startTime = 1553585363933 Thread-2 wait startTime = 1553585363933 Thread-1 notify startTime = 1553585368934 Thread-1 notify endTime = 1553585368934 Thread-2 wait endTime = 1553585368934 Thread-0 wait endTime = 1553585368935
可以看到,thread02中的notifyAll()方法唤醒了thread01和thread03。
wait()方法可以使调用该线程的方法释放共享资源的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。
notify()方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使得该线程退出等待状态,进入可运行状态
notifyAll()方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态
最后,如果wait()方法和notify()/notifyAll()方法不在同步方法/同步代码块中被调用,那么虚拟机会抛出java.lang.IllegalMonitorStateException,注意一下。
三、关于wait()和notify()是否释放锁的验证
先上结论,wait()执行时会释放对象锁,notify()执行时不会释放锁。
举例1:验证wait()执行时会释放锁
thread01中调用wait()方法,for循环是为了wait()方法执行完成后,延迟方法的结束时间,看一下是否有别的线程进来
public class Thread01 extends Thread{ private Object object; private List<Integer> list = new ArrayList<>(); public Thread01(Object object) { this.object = object; } @Override public void run() { synchronized(object){ try { System.out.println(Thread.currentThread().getName() + " wait startTime = " + System.currentTimeMillis()); object.wait(); for(int i = 0; i < 50000000; i++) { list.add(i); } System.out.println(Thread.currentThread().getName() + " wait endTime = " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
thread02中调用notify()方法
public class Thread02 extends Thread{ private Object object; public Thread02(Object object) { this.object = object; } @Override public void run() { synchronized(object){ System.out.println(Thread.currentThread().getName() + " notify startTime = " + System.currentTimeMillis()); object.notify(); System.out.println(Thread.currentThread().getName() + " notify endTime = " + System.currentTimeMillis()); } } }
测试一下,多开几个thread01线程对象
public class Test { public static void main(String[] args) throws InterruptedException { Object object = new Object(); Thread01 thread010 = new Thread01(object); Thread01 thread011 = new Thread01(object); Thread01 thread012 = new Thread01(object); Thread01 thread013 = new Thread01(object); Thread01 thread014 = new Thread01(object); Thread02 thread02 = new Thread02(object); thread010.start(); thread011.start(); thread012.start(); thread013.start(); thread014.start(); Thread.sleep(1000); thread02.start(); } }
结果:
说明:在Thread-1执行完wait()方法后,在执行for循环的时候,Thread-2就进来了,说明Thread-1已经不再拥有object的对象锁,也就是说,wait()方法执行结束后会释放锁。此处使用的是notify()方法,所以只会唤醒一个线程,其余线程都会处于等待状态。
举例2:验证notify()执行时不会释放锁
thread01中调用wait()方法
public class Thread01 extends Thread{ private Object object; public Thread01(Object object) { this.object = object; } @Override public void run() { synchronized(object){ try { System.out.println(Thread.currentThread().getName() + " wait startTime = " + System.currentTimeMillis()); object.wait(); System.out.println(Thread.currentThread().getName() + " wait endTime = " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
thread02中调用notify()方法,for循环是为了notidy()方法执行完成后,延迟方法的结束时间,看一下是否有别的线程进来
public class Thread02 extends Thread{ private Object object; private List<Integer> list = new ArrayList<>(); public Thread02(Object object) { this.object = object; } @Override public void run() { synchronized(object){ System.out.println(Thread.currentThread().getName() + " notify startTime = " + System.currentTimeMillis()); object.notify(); for(int i = 0; i < 50000000; i++) { list.add(i); } System.out.println(Thread.currentThread().getName() + " notify endTime = " + System.currentTimeMillis()); } } }
测试一下,多开几个thread02线程对象
public class Test { public static void main(String[] args) throws InterruptedException { Object object = new Object(); Thread01 thread01 = new Thread01(object); Thread02 thread020 = new Thread02(object); Thread02 thread021 = new Thread02(object); Thread02 thread022 = new Thread02(object); Thread02 thread023 = new Thread02(object); thread01.start(); thread020.start(); thread021.start(); thread022.start(); thread023.start(); } }
结果:
Thread-0 wait startTime = 1553587819923 Thread-1 notify startTime = 1553587819923 Thread-1 notify endTime = 1553587833100 Thread-0 wait endTime = 1553587833100 Thread-4 notify startTime = 1553587833100 Thread-4 notify endTime = 1553587840268 Thread-3 notify startTime = 1553587840268 Thread-3 notify endTime = 1553587862791 Thread-2 notify startTime = 1553587862791 Thread-2 notify endTime = 1553587877195
说明:以Thread-4为例,其notify()方法结束后,在执行for循环的期间,Thread-3和Thread-2并没有与Thread-4并发执行,说明Thread-4此时还拥有object的对象锁,也就是说,notify()方法并没有释放锁。notifyAll()方法也是一样,这里不再举例说明了。