线程之间的通信可以通过共享内存变量的方式进行相互通信,也可以使用 api 提供的 wait(),notify()实现线程之间的通信。wait() 方法是 Object 类的方法,改方法用来将当前的线程置入 "预执行队列" 中,并且在 wait() 方法代码处停止执行进行等待,知道接收到同一个 monitor 对象的 notify() 或者 notifyAll() 方法的通知或者是收到中断。在调用 wait() 方法之前,线程必须获得锁,即只能在同步方法或者同步块中调用 wait() 方法,在执行 wait() 后,当前线程释放锁。在 wait() 方法释放锁后,其他处于等待该监视器对象的锁的线程将相互竞争获得此监视器对象锁。如果没有同步块或者同步方法,那么调用该方法将会抛 IllegalMonitorStateException. 并且由于可能发生中断或者是虚假唤醒,那么最好将 wait() 方法放在循环中调用。下面是 api 文档:
- public final void wait()
- throws InterruptedException
- Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for thisobject.In other words,thismethod behaves exactly asifit simply performs the call wait(0).
- The current thread must own thisobject's monitor.The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitorto wake up either through a call to the notify method or the notifyAll method. The thread then waitsuntil it can re-obtain ownership of the monitor and resumes execution.
- As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
- synchronized (obj) {
- while()
- obj.wait();
- ... // Perform action appropriate to condition
- }
- This method should only be called by a thread that is the owner of thisobject's monitor.See the notify method for a description of the ways in which a thread can become the owner of a monitor.Throws:
- IllegalMonitorStateException -ifthe current thread is not the owner of the object's monitor.
- InterruptedException -ifany thread interrupted the current thread before orwhilethe current thread was waitingfora notification.The interrupted status of the current thread is cleared whenthis exception is thrown.
- See Also:
- notify(), notifyAll()
下面是 wait(long timeout) 的 api:
- public final voidwait(long timeout)
- throws InterruptedException
- Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
- The current thread must own thisobject's monitor.
- This method causes the current thread (call it T) to place itself in the wait set for thisobject and then to relinquish any and all synchronization claims onthisobject. Thread T becomes disabledfor thread scheduling purposes and lies dormant until one of four things happens:
- Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened.
- Some other thread invokes the notifyAll method for this object.
- Some other thread interrupts thread T.
- The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified.
- The thread T is then removed from the wait set for thisobject and re-enabledforthread scheduling. It then competes in the usual manner with other threadsforthe right to synchronize on the object; once it has gained control of the object, all its synchronization claims on the object are restored to the status quo ante - that is, to the situation as of the time that the wait method was invoked. Thread T then returns from the invocation of the wait method. Thus, onreturn from the wait method, the synchronization state of the object and of thread T is exactly as it was when the wait method was invoked.
- A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. Whilethiswill rarely occur in practice, applications must guard against it by testingforthe condition that should have caused the thread to be awakened, and continuing to waitifthe condition is not satisfied. In other words, waits should always occur in loops, likethis one:
- synchronized (obj) {
- while()
- obj.wait(timeout);
- ... // Perform action appropriate to condition
- }
- (For more information on thistopic, see Section 3.2.3 in Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).
- If the current thread is interrupted by any thread before or whileit is waiting, then an InterruptedException is thrown. This exception is not thrown until the lock status ofthis object has been restored as described above.
- Note that the wait method, as it places the current thread into the wait set for thisobject, unlocks onlythisobject; any other objects on which the current thread may besynchronizedremain lockedwhile the thread waits.
- This method should only be called by a thread that is the owner of thisobject's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor.
- Parameters:
- timeout - the maximum time to wait in milliseconds.
- Throws:
- IllegalArgumentException -if the value of timeout is negative.
- IllegalMonitorStateException -ifthe current thread is not the owner of the object's monitor.
- InterruptedException -ifany thread interrupted the current thread before orwhilethe current thread was waitingfora notification. The interrupted status of the current thread is cleared whenthis exception is thrown.
- See Also:
- notify(), notifyAll()
方法 notify()也要在同步方法或同步块中使用,该方法用来通知那些处于 wait() 方法的线程,并且这 notify() 和 wait() 方法的监视器对象应该是同一个,才可以进行唤醒。如果有多个线程处于 wait() 的等待中,那么也只能抽取一个进行唤醒,使 wait() 方法获取锁对象和继续执行。需要注意的是:在执行完 notify() 方法后,当前线程不会马上释放对象锁,wait() 的线程也不会马上拥有锁。要等到 notify() 方法所在的线程将程序执行完成后,也就是退出同步快或者同步方法后才释放锁,继而 wait() 才获得对象锁。
也就是说:notify() 操作可以唤醒一个因为调用了 wait() 方法而处于阻塞状态的线程,使其处于就绪状态。
下面是 notify()api:
- public final void notify()
- Wakes up a single thread that is waiting on thisobject'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 wait methods.
- The awakened thread will not be able to proceed until the current thread relinquishes the lock on thisobject. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize onthisobject;forexample, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lockthis object.
- This method should only be called by a thread that is the owner of thisobject's monitor. A thread becomes the owner of the object's monitor in one of three ways:
- By executing a synchronized instance method of that object.
- By executing the body of a synchronized statement that synchronizes on the object.
- For objects of type Class, by executing a synchronized staticmethod of thatclass.
- Only one thread at a time can own an object's monitor.
- Throws:
- IllegalMonitorStateException -ifthe current thread is not the owner ofthisobject's monitor.See Also:
- notifyAll(), wait()
例子:
- package soarhu;
- class Service{
- private Object lock;
- public Service(Object lock) {
- this.lock = lock;
- }
- void waiting(){
- synchronized (lock){
- System.out.println("waiting enter..."+System.currentTimeMillis());
- try {
- lock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("waiting outer...."+System.currentTimeMillis());
- }
- }
- void notifying(){
- synchronized (lock){
- System.out.println("notifying enter..."+System.currentTimeMillis());
- try {
- Thread.sleep(3000);
- lock.notify();
- System.out.println("notifying outer..."+System.currentTimeMillis());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public class Test {
- public static voidmain(String[] args)throws InterruptedException {
- Object o =new Object();
- Service service =new Service(o);
- Thread t1 =new Thread(){
- @Override
- public void run() {
- service.waiting();
- }
- };
- Thread t2 =new Thread(){
- @Override
- public void run() {
- service.notifying();
- }
- };
- t1.start();
- Thread.sleep(1000);
- t2.start();
- }
- }
输出结果:
- waiting enter...1492483779692
- notifying enter...1492483780692
- notifying outer...1492483783699
- waiting outer....1492483783699
如果先启动 t2 线程那么就可能造成死锁。或者不能确保 t1 线程先启动的话,死锁必然发生。
下面修改 notify() 方法,验证 notify() 必须执行完后才释放锁
- void notifying(){
- synchronized (lock){
- System.out.println("notifying enter..."+System.currentTimeMillis());
- try {
- Thread.sleep(3000);
- lock.notify();
- for(inti = 0; i < 10; i++) {
- System.out.println(i);
- }
- System.out.println("notifying outer..."+System.currentTimeMillis());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
输出结果:
- waiting enter...1492484010466
- notifying enter...1492484011466
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- notifying outer...1492484014466
- waiting outer....1492484014466
可以看到,即使 notify() 在循环前调用,但仍要使所在方法执行完成之后才释放监视器锁。
那么如何避免可能发生的死锁呢?修改代码如下:
- package soarhu;
- class Service{
- private Object lock;
- private booleanwaitFirst =true;
- public Service(Object lock) {
- this.lock = lock;
- }
- void waiting(){
- synchronized (lock){
- while (waitFirst){
- System.out.println("waiting enter..."+System.currentTimeMillis());
- try {
- lock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("waiting outer...."+System.currentTimeMillis());
- }
- }
- }
- void notifying(){
- synchronized (lock){
- System.out.println("notifying enter..."+System.currentTimeMillis());
- try {
- Thread.sleep(3000);
- lock.notify();
- for(inti = 0; i < 10; i++) {
- System.out.println(i);
- }
- System.out.println("notifying outer..."+System.currentTimeMillis());
- waitFirst=false;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public class Test {
- public static voidmain(String[] args)throws InterruptedException {
- Object o =new Object();
- Service service =new Service(o);
- Thread t1 =new Thread(){
- @Override
- public void run() {
- service.waiting();
- }
- };
- Thread t2 =new Thread(){
- @Override
- public void run() {
- service.notifying();
- }
- };
- t2.start();
- Thread.sleep(1000);
- t1.start();
- }
- }
输出结果:
- notifying enter...1492486139592
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- notifying outer...1492486142592
可以看到 waitting() 方法没有被执行了,从而避免了可能发生死锁问题,但是虽然避免了死锁那么 wait/antify 的意义也就不存在了。
notify() 与 notifyAll()区别
wait() 表示: 放弃当前对资源的占有权,等啊等啊,一直等到有人通知我,我才会运行后面的代码。 notify() 表示: 当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从 wait 状态中恢复, 然后继续运行 wait() 后面的语句; notifyAll() 表示: 当前的线程已经放弃对资源的占有,通知所有的等待线程从 wait() 方法后的语句开始运行。
例子:
- package other;
- class Service{
- private Object lock;
- public Service(Object lock) {
- this.lock = lock;
- }
- void waiting(){
- synchronized (lock){
- System.out.println("waiting enter...");
- try {
- lock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("waiting outer....");
- }
- }
- void notifying(){
- synchronized (lock){
- System.out.println("notifying enter...");
- try {
- Thread.sleep(1000);
- lock.notify();
- System.out.println("notifying outer...");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public class Test {
- public static voidmain(String[] args)throws InterruptedException {
- Object o =new Object();
- Service service =new Service(o);
- for(inti = 0; i < 2; i++) {
- new Thread(){
- @Override
- public void run() {
- service.waiting();
- }
- }.start();
- }
- Thread.sleep(1000);
- Thread t2 =new Thread(){
- @Override
- public void run() {
- service.notifying();
- }
- };
- t2.start();
- }
- }
输出结果:产生死锁。有一个没出来。
- waiting enter...
- waiting enter...
- notifying enter...
- notifying outer...
- waiting outer....
改为 notifyAll() 后:
- void notifying(){
- synchronized (lock){
- System.out.println("notifying enter...");
- try {
- Thread.sleep(1000);
- lock.notifyAll();
- System.out.println("notifying outer...");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
输出结果:
- waiting enter...
- waiting enter...
- notifying enter...
- notifying outer...
- waiting outer....
全部退出 wait() 方法。
notifyAll()/wait() 中。if while 的事情。
- package soarhu;
- import java.util.ArrayList;
- import java.util.List;
- class Service{
- private Object lock;
- privateList list = newArrayList<>();
- public Service(Object lock) {
- this.lock = lock;
- }
- void waiting(){
- synchronized (lock){
- try {
- if(list.size()==0){//此处错误的用法,应该用whileSystem.out.println("wait enter..."+Thread.currentThread().getName());
- lock.wait();
- System.out.println("wait outer..."+Thread.currentThread().getName());
- }
- list.remove(0);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("size: "+list.size());
- }
- }
- void notifying(){
- synchronized (lock){
- System.out.println("ADD enter..."+Thread.currentThread().getName());
- list.add("hello");
- lock.notifyAll();
- System.out.println("ADD outer..."+Thread.currentThread().getName());
- }
- }
- }
- public class Test {
- public static voidmain(String[] args)throws InterruptedException {
- Object o =new Object();
- Service service =new Service(o);
- for(inti = 0; i < 2; i++) {
- new Thread(){
- @Override
- public void run() {
- service.waiting();
- }
- }.start();
- }
- Thread.sleep(1000);
- for(inti = 0; i < 2; i++) {
- new Thread(){
- @Override
- public void run() {
- service.notifying();
- }
- }.start();
- }
- }
- }
输出结果:抛出异常。。。
- wait enter...Thread-0//thread-0 等待
- wait enter...Thread-1//thread-1 等待
- ADD enter...Thread-2
- Exception in thread "Thread-0"java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
- at java.util.ArrayList.rangeCheck(ArrayList.java:653)
- at java.util.ArrayList.remove(ArrayList.java:492)
- ADD outer...Thread-2
- wait outer...Thread-1//thread-1 退出
- size: 0
- wait outer...Thread-0//thread-0 退出,再此过程中抛出异常
- at soarhu.Service.waiting(Test.java:23)
- ADD enter...Thread-3
- ADD outer...Thread-3
- at soarhu.Test$1.run(Test.java:50)
- Process finished with exit code 0
分析结果:
开始线程 0 和线程 2 进入,判断得知 list 的长度为 0 则进行等待。然后线程 2 开始执行添加一个元素,并唤醒线程 0 和线程 1. 唤醒后,线程 1 执行 remove() 使 list.size() 为 0. 然后线程 1 从 wait() 方法处继续执行,再次执行 remove. 而此时 size 已经为 0. 所以会抛异常。而如果将 if 改为 while,那么结果将正确,因为这样每次 wait() 被唤醒后每次都会检测 size 的大小。如果已经被别的线程修改过了,那么将继续 wait().
- void waiting(){
- synchronized (lock){
- try {
- while(list.size()==0){//okSystem.out.println("wait enter..."+Thread.currentThread().getName());
- lock.wait();
- System.out.println("wait outer..."+Thread.currentThread().getName());
- }
- list.remove(0);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("size: "+list.size());
- }
- }
输出结果:
- wait enter...Thread-0//thread-0 等待
- wait enter...Thread-1//thread-1 等待
- ADD enter...Thread-2
- ADD outer...Thread-2
- wait outer...Thread-1//thread-1 退出
- size: 0
- wait outer...Thread-0//thread-0 退出
- wait enter...Thread-0//thread-0 继续等待
- ADD enter...Thread-3
- ADD outer...Thread-3
- wait outer...Thread-0//thread-0 退出
- size: 0
- Process finished with exit code 0
分析结果可知,
开始线程 0 和线程 2 进入,判断得知 list 的长度为 0 则进行等待。然后线程 2 开始执行添加一个元素,并唤醒线程 0 和线程 1. 唤醒后,线程 1 执行 remove() 使 list.size() 为 0. 然后线程 1 从 wait() 方法处继续执行,
此时再次进行 size() 判断,得知为 0,继续等待。线程 3 进行加 1,线程 0 现在才有机会退出等待。如果 add 的线程数少,那么又会发生死锁。改写代码:
- public class Test {
- public static voidmain(String[] args)throws InterruptedException {
- Object o =new Object();
- Service service =new Service(o);
- for(inti = 0; i < 2; i++) {//这里的数量为2
- new Thread(){
- @Override
- public void run() {
- service.waiting();
- }
- }.start();
- }
- Thread.sleep(1000);
- for(inti = 0; i < 1; i++) {//此时数量改为1
- new Thread(){
- @Override
- public void run() {
- service.notifying();
- }
- }.start();
- }
- }
- }
输出结果:死锁
- wait enter...Thread-0
- wait enter...Thread-1
- ADD enter...Thread-2
- ADD outer...Thread-2
- wait outer...Thread-1
- size: 0
- wait outer...Thread-0
- wait enter...Thread-0
分析:可以对比上次的执行结果。线程 0 再次进入等待后,此时没有新的线程来通知他进行唤醒,即使唤醒了,那么如果 list 中没有元素依然要进行等待。
来源: http://www.cnblogs.com/soar-hu/p/6727201.html