Java并发之线程间协作Object的wait()、notify()、notifyAll()
1)wait()、notify()和notifyAll()方法是本地方法,而且为final方法,无法被重写。
2)调用某个对象的wait()方法能让当前线程堵塞。而且当前线程必须拥有此对象的monitor(即锁)
3)调用某个对象的notify()方法可以唤醒一个正在等待这个对象的monitor的线程,假设有多个线程都在等待这个对象的 monitor。则仅仅能唤醒当中一个线程;4)调用notifyAll()方法可以唤醒全部正在等待这个对象的monitor的线程;
为何这三个不是Thread类声明中的方法。而是Object类中声明的方法(当然因为Thread类继承了Object类,所以Thread也能够调用者三个方法)?事实上这个问题非常easy。因为每一个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁。当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁。假设通过线程来操作。就非常复杂了。
上面已经提到,假设调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待兴许再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停运行一段时间。从而让其它线程有机会继续运行。但它并不释放对象锁);
notify()方法可以唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话。则仅仅能唤醒当中一个线程。详细唤醒哪个线程则不得而知。
相同地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor。因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
nofityAll()方法可以唤醒全部正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
这里要注意一点:notify()和notifyAll()方法仅仅是唤醒等待该对象的monitor的线程。并不决定哪个线程可以获取到monitor。
举个简单的样例:假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3仅仅有一个能被唤醒。
注意。被唤醒不等于立马就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来可以获取到objectA的monitor就详细依赖于操作系统的调度了。
上面尤其要注意一点。一个线程被唤醒不代表马上获取了对象的monitor。仅仅有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁运行。
package com.gpl.concurrent.lock; public class Test { public static Object object = new Object(); public static void main(String[] args) { Thread1 thread1 = new Thread1(); Thread2 thread2 = new Thread2(); thread1.setName("我是Thread1"); thread2.setName("我是Thread2"); thread1.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread2.start(); } static class Thread1 extends Thread{ @Override public void run() { synchronized (object) { try { String name=Thread.currentThread().getName(); System.out.println(name+"运行了"); object.wait(); System.out.println(name+"继续运行"); } catch (InterruptedException e) { } System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁"); } } } static class Thread2 extends Thread{ @Override public void run() { synchronized (object) { String name=Thread.currentThread().getName(); System.out.println(name+"运行了"); object.notify(); System.out.println("线程"+Thread.currentThread().getName()+"调用了object.notify()"); } System.out.println("线程"+Thread.currentThread().getName()+"释放了锁"); } } }结果:
在Java中,能够通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信。在线程中调用wait()方法。将堵塞等待其它线程的通知(其它线程调用notify()方法或notifyAll()方法),在线程中调用notify()方法或notifyAll()方法,将通知其它线程从wait()方法处返回。
Object是全部类的超类,它有5个方法组成了等待/通知机制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。
在Java中,全部的类都从Object继承而来。因此,全部的类都拥有这些共同拥有方法可供使用。并且。因为他们都被声明为final。因此在子类中不能覆写不论什么一个方法。
这里具体说明一下各个方法在使用中须要注意的几点:
1、wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException
该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前。线程必需要获得该对象的对象级别锁,即仅仅能在同步方法或同步块中调用wait()方法。
进入wait()方法后。当前线程释放锁。在从wait()返回前,线程与其它线程竞争又一次获得锁。假设调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException。它是RuntimeException的一个子类。因此,不需要try-catch结构。
2、notify()
public final native void notify() throws IllegalMonitorStateException
该方法也要在同步方法或同步块中调用,即在调用前。线程也必需要获得该对象的对象级别锁。的假设调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。
该方法用来通知那些可能等待该对象的对象锁的其它线程。假设有多个线程等待,则线程规划器随意挑选出当中一个wait()状态的线程来发出通知。并使它等待获取该对象的对象锁(notify后,当前线程不会立即释放该对象锁。wait所在的线程并不能立即获取该对象锁。要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才干够获取该对象锁),但不惊动其它相同在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程执行完成以后,它会释放掉该对象锁,此时假设该对象没有再次使用notify语句,则即便该对象已经空暇。其它wait状态等待的线程因为没有得到该对象的通知,会继续堵塞在wait状态。直到这个对象发出一个notify或notifyAll。
这里须要注意:它们等待的是被notify或notifyAll,而不是锁。这与以下的notifyAll()方法执行后的情况不同。
3、notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
该方法与notify()方法的工作方式同样。重要的一点差异是:
notifyAll使全部原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒。不再等待notify或notifyAll,但因为此时还没有获取到该对象锁,因此还不能继续往下运行)。变成等待获取该对象上的锁。一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候)。他们就会去竞争。假设当中一个线程获得了该对象锁,它就会继续往下运行,在它退出synchronized代码块。释放锁后,其它的已经被唤醒的线程将会继续竞争获取该锁。一直进行下去。直到全部被唤醒的线程都运行完成。
4、wait(long)和wait(long,int)
显然。这两个方法是设置等待超时时间的,后者在超值时间上加上ns。精度也难以达到。因此,该方法非常少使用。对于前者。假设在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争又一次获得锁。并从wait(long)返回。另外。须要知道,假设设置了超时时间。当wait()返回时。我们不能确定它是由于接到了通知还是由于超时而返回的,由于wait()方法不会返回不论什么相关的信息。
但一般能够通过设置标志位来推断,在notify之前改变标志位的值。在wait()方法后读取该标志位的值来推断,当然为了保证notify不被遗漏,我们还须要另外一个标志位来循环推断是否调用wait()方法。
深入理解:
假设线程调用了对象的wait()方法。那么线程便会处于该对象的等待池中。等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的notifyAll()方法(唤醒全部wait线程)或notify()方法(仅仅随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法。它才会又一次回到等待池中。而竞争到对象锁的线程则继续往下运行。直到运行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
參考:http://www.cnblogs.com/dolphin0520/p/3920385.html