为啥wait,notify和notifyAll必须在同步块或同步方法中调

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为啥wait,notify和notifyAll必须在同步块或同步方法中调相关的知识,希望对你有一定的参考价值。

参考技术A wait配合同步块或同步方法进行阻塞,释放锁。
notify和notifyAll在同步块或同步方法中调用,可以唤醒阻塞的线程,让它们与其他线程再竞争锁,如果放到同步块外调用,则不是公平的,可能被被其他线程优先获取锁。

Java并发之线程间协作Object的wait()notify()notifyAll()

wait()、notify()和notifyAll()是Object类中的方法:

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

以上是关于为啥wait,notify和notifyAll必须在同步块或同步方法中调的主要内容,如果未能解决你的问题,请参考以下文章

Java线程的wait(), notify()和notifyAll()

为什么wait(),notify()和notifyAll()必须在同步块或同步方法中调

(转)为什么wait(),notify()和notifyAll()必须在同步块或同步方法中调用

sleep,yield,wait,notify,notifyAll

为什么 wait(), notify()和 notifyAll ()必须在同步方法或 者同步块中被调用?

为什么 wait(), notify()和 notifyAll ()必须在同步方法或 者同步块中被调用?