zoukankan      html  css  js  c++  java
  • 多线程-wait/notify/notifyAll

    引言

    在Java中,可以通过配合调用Object对象的wait,notify和notifyAll来实现线程间的通信。

    在线程中调用wait方法,将阻塞带带其他线程的通知(其他线程调用notify或notifyAll)。

    在线程中调用notify或notifyAll将通知其他线程从wait方法处返回。

    Object是所有类的父类,它有5个方法组成了等待/通知机制的核心:notify(),notifyAll(),wait(),wait(long)和wait(long, int)。

    在Java中,所有的类都从Object继承而来,因此所有的类都拥有这些共有方法。而且由于他们都被声明为final,因此在子类中不能覆写任何一个方法。

    public class Object {
        public final native void notify();
        public final native void notifyAll();
        public final void wait() throws InterruptedException {
            wait(0);
        }    
        public final native void wait(long timeout) throws InterruptedException;
        public final void wait(long timeout, int nanos) throws InterruptedException {
            if (timeout < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                                    "nanosecond timeout value out of range");
            }
    
            if (nanos > 0) {
                timeout++;
            }
    
            wait(timeout);
        }
    }
    

    wait  

    该方法用来将当前线程置于休眠状态,直到接到通知或被中断,在调用wait方法之前,线程必须要获得该对象级别锁,否则编译可以通过,但是运行会抛出异常IllegalMonitorStateException(它是RuntimeException的一个子类,不需要try-catch结构),即只能在同步方法或同步块中调用wait。进入wait方法后,当前线程阻塞,并立刻释放锁。在从wait返回前,线程与其他线程竞争重新获得锁。

    (1)线程必须要获得该对象级别锁

    package com.huawei.thread;
    
    public class Test36 {
    
    	public static void main(String[] args) throws InterruptedException {
    		Object obj = new Object();
    		obj.wait();
    	}
    
    }

    运行截图:

    wait(long)/wait(long, int)

    显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。 

    notify

    (1)该方法也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁,如果调用notify时没有持有适当的锁,也会抛出IllegalMonitorStateException。

     实例:调用lock对象的notify方法,但是获取的锁却是obj对象,所以抛出异常。也就是说,要在某个对象上执行wait、notify方法,先必须锁定该对象。

    package com.huawei.thread;
    
    public class Test36 {
    
    	public static void main(String[] args) throws InterruptedException {
    		Object obj = new Object();
    		Object lock = new Object();
    		synchronized (obj) {
    			lock.notify();
    		}
    	}
    }

    运行截图:

    (2)该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait状态的线程来发出通知,并使它等待获取该对象的对象锁。

    package com.huawei.thread;
    
    public class Test36 {
    
    	public static void main(String[] args) throws InterruptedException {
    		Object obj = new Object();
    		Thread a = new Thread(new A(obj));
    		Thread b = new Thread(new B(obj));
    		a.setName("a");
    		b.setName("b");
    		a.start();
    		b.start();
    		Thread.sleep(2000);
    		synchronized (obj) {
    			obj.notify();
    		}
    	}
    }
    
    class A implements Runnable {
    	private Object obj;
    
    	public A(Object obj) {
    		this.obj = obj;
    	}
    
    	@Override
    	public void run() {
    		synchronized (obj) {
    			try {
    				System.out.println(Thread.currentThread().getName() + " is waiting...");
    				obj.wait();
    				System.out.println(Thread.currentThread().getName() + " is end...");
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    class B implements Runnable {
    	private Object obj;
    
    	public B(Object obj) {
    		this.obj = obj;
    	}
    
    	@Override
    	public void run() {
    		synchronized (obj) {
    			try {
    				System.out.println(Thread.currentThread().getName() + " is waiting...");
    				obj.wait();
    				System.out.println(Thread.currentThread().getName() + " is end...");
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }  

    运行截图:(1)首先a和b分别进入wait阻塞(2)2秒之后main线程调用notify通知了一个线程,a被唤醒执行(3)由于notify只能唤醒一个线程,所以b就永久阻塞了

    可以看出:当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。 

    (3)notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到notify所在线程退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才能可以获取该对象锁。

    package com.huawei.thread;
    
    import java.util.Date;
    
    public class Test36 {
    
    	public static void main(String[] args) throws InterruptedException {
    		Object obj = new Object();
    		Thread a = new Thread(new A(obj));
    		Thread b = new Thread(new B(obj));
    		a.setName("a");
    		b.setName("b");
    		a.start();
    		b.start();
    		Thread.sleep(2000);
    		synchronized (obj) {
    			obj.notifyAll();
    			System.out.println(new Date());
    			Thread.sleep(3000);
    		}
    	}
    }
    
    class A implements Runnable {
    	private Object obj;
    
    	public A(Object obj) {
    		this.obj = obj;
    	}
    
    	@Override
    	public void run() {
    		synchronized (obj) {
    			try {
    				System.out.println(Thread.currentThread().getName() + " is waiting...");
    				obj.wait();
    				System.out.println(Thread.currentThread().getName() + " is end at: " + new Date());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    class B implements Runnable {
    	private Object obj;
    
    	public B(Object obj) {
    		this.obj = obj;
    	}
    
    	@Override
    	public void run() {
    		synchronized (obj) {
    			try {
    				System.out.println(Thread.currentThread().getName() + " is waiting...");
    				obj.wait();
    				System.out.println(Thread.currentThread().getName() + " is end at: " + new Date());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }  

    运行截图:可以看出主线程notifyAll之后会sleep(3000),然后才会释放锁,b线程也是在3秒之后才能获取到锁。

    在实际编程中,我们应该尽量在线程调用notify/notifyAll后,立即退出临界区释放锁,即不要在notify/notifyAll后面再写一些耗时的代码。

    另外:如果一个对象之前没有调用wait方法,那么调用notify方法是没有任何影响的。

    notifyAll

    notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。 

    wait/notify模式

    通常,多线程之间需要协调工作:如果条件不满足,则等待;当条件满足时,等待该条件的线程将被唤醒,在Java中,这个模式的实现依赖wait/notify。

    synchronized(obj) {
      while(!condition) {
         obj.wait();
      }
      obj.doSomething();
    }  

    当线程A获得obj锁后,发现条件condition不满足,无法继续下一步处理,于是线程A就wait;

    另一个线程B中,如果B改改了某些条件,使得线程A的condition条件满足,就可以唤醒线程A:

    synchronized(obj) {
      condition = true;
      obj.notifyAll();
    } 

    为什么条件测试使用while,而不是if?

    实例:有两个线程从List中删除数据,而只有一个线程向List中添加数据。初始时,List为空,只有往List中添加了数据之后,才能删除List中的数据。添加数据的线程向List添加完数据后,调用notifyAll(),唤醒了两个删除线程,但是它只添加了一个数据,而现在有两个唤醒的删除线程,这时如果使用if会怎样?

    结论:如果用 if 测试List中的数据的个数,则会出现IndexOutofBoundException。越界异常。原因是,List中只有一个数据,第一个删除线程把数据删除后,第二个线程再去执行删除操作时,删除失败,从而抛出 IndexOutofBoundException。

    package com.huawei.thread;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Test37 {
    	public static List<String> list = new ArrayList<String>();
    
    	public static void main(String[] args) throws InterruptedException {
    		Object lock = new Object();
    
    		SubtractThread sub1 = new SubtractThread(lock);
    		sub1.setName("sub1");
    
    		SubtractThread sub2 = new SubtractThread(lock);
    		sub2.setName("sub2");
    
    		sub1.start();
    		sub2.start();
    
    		Thread.sleep(2000);
    		AddThread adder = new AddThread(lock);
    		adder.setName("adder");
    		adder.start();
    
    	}
    
    }
    
    class AddThread extends Thread {
    	private Object lock;
    
    	public AddThread(Object lock) {
    		this.lock = lock;
    	}
    
    	@Override
    	public void run() {
    		synchronized (lock) {
    			Test37.list.add("test");
    			lock.notifyAll();
    		}
    	}
    }
    
    class SubtractThread extends Thread {
    	private Object lock;
    
    	public SubtractThread(Object lock) {
    		this.lock = lock;
    	}
    
    	@Override
    	public void run() {
    		try {
    			synchronized (lock) {
    				if (Test37.list.isEmpty()) {//将这里的if改成while即可保证不出现越界异常
    					System.out.println("wait begin thread name: " + Thread.currentThread().getName());
    					lock.wait();
    					System.out.println("wait end thread name: " + Thread.currentThread().getName());
    				}
    				Test37.list.remove(0);
    				System.out.println("list size=" + Test37.list.size());
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }

    运行截图:

    另外:

    (1)如果要把wait和notify放在一起使用的话,必须先调用notify方法,后调用wait。

    (2)假设线程A执行wait,线程B执行notify:如果B先执行了notify然后结束,A执行wait阻塞,那此时A将无法被正常唤醒。(当然可以通过interrupt()方法以抛出异常的方式唤醒A) 

    wait与中断

    请参考《多线程-interrupt(),isInterrupted(),interrupted()》  

    小结

    (1)如果线程调用了对象的wait方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

    (2)当有线程调用了对象的notifyAll方法(唤醒所有wait线程)或notify方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

    (3)优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中

    (4)唯有线程再次调用wait方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

    参考资料

    http://blog.csdn.net/ns_code/article/details/17225469

    http://blog.csdn.net/oracle_microsoft/article/details/6863662

    http://www.cnblogs.com/hapjin/p/5492645.html

    http://www.cnblogs.com/pangyang/articles/5916349.html

  • 相关阅读:
    git(1)-git关联GitHub-windows-转载
    jenkins(4)-jenkins配置邮件通知
    jenkins(3)-linux下安装jenkins(yum install方式)
    【PAT甲级】1090 Highest Price in Supply Chain (25 分)(DFS)
    【PAT甲级】1087 All Roads Lead to Rome (30 分)(MAP【int,string】,邻接表,DFS,模拟,SPFA)
    【PAT甲级】1018 Public Bike Management (30 分)(DFS,SPFA)
    Educational Codeforces Round 61 (Rated for Div. 2) G(线段树,单调栈)
    Atcoder Grand Contest 032C(欧拉回路,DFS判环)
    Educational Codeforces Round 62 (Rated for Div. 2)E(染色DP,构造,思维,组合数学)
    Atcoder Grand Contest 031C(构造,思维,异或,DFS)
  • 原文地址:https://www.cnblogs.com/lujiango/p/7656317.html
Copyright © 2011-2022 走看看