java notify()和notifyall()的区别&wait()方法的使用

notify()&notifyall()的共同点:均能唤醒正在等待的线程,并且均是最后只有一个线程获取资源对象的锁。

       不同点:notify() 只能唤醒一个线程,而notifyall()能够唤醒所有的线程,当线程被唤醒以后所有被唤醒的线程竞争获取资源对象的锁,其中只有一个能够得到对象锁,执行代码。

      注意:wait()方法并不是在等待资源的锁,而是在等待被唤醒(notify()),一旦被唤醒后,被唤醒的线程就具备了资源锁(因为无需竞争),直至再次执行wait()方法或者synchronized代码块执行完毕。

下文的代码由于只使用notify()没有使用notifyall()而导致出现死锁。

原因如下:假如A线程是sub线程,有两个线程B、C是main线程,刚开始isSub=true,则A线程首先获得当前对象的锁,A线程执行完毕后,执行notify()方法,这时候只唤醒了B,B执行完后,将isSub改为了true,此时B可能又通过notify()方法唤醒了C,则C由于执行main方法先会判断while(isSub),判断完后又进入了等待状态,这时候又没有任何一个线程来唤醒,因此出现了死锁。

而将notify改为notifyall()后,线程B进入等待状态,释放对象锁,这时候AC开始竞争获取对象锁,假如C获取对象锁,则C经过判断也进入等待状态,此时A开始工作。

从上述步骤中,我们也可以发现wait notify()通信机制的弊端,即:执行notify()方法的线程并不知道被唤醒的对象是谁。

    

public class OutTurn {
	 private boolean isSub = true;
	 private int count=0;

	 public synchronized void sub() {
	  try {
	   while (!isSub) {
	    this.wait();
	   }
	   System.out.println("sub ---- "+count);
	   isSub=false;
	   this.notify();
	  } catch (Exception e) {
	   e.printStackTrace();
	  }
	  count++;

	 }

	 public synchronized void main() {
	  try {
	   while(isSub){
	    this.wait();
	   }
	   System.out.println("main (((((((((((( "+count);
	   isSub=true;
	   this.notify();
	  } catch (Exception e) {
	   e.printStackTrace();
	  }
	  count++;
	 }
	}
public class LockDemo {
 public static void main(String[] args) {
  final OutTurn ot = new OutTurn();
  
  for(int j=0;j<100;j++){

   new Thread(new Runnable() {

    public void run() {
     for (int i = 0; i <5; i++) {     
      ot.sub();
     }
    }
   }).start();

   new Thread(new Runnable() {

    public void run() {
     for (int i = 0; i < 5; i++) {     
      ot.main();
     }
    }
   }).start();
  }

 }
}

永远在循环(loop)里调用 wait 和 notify,不是在 If 语句

现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!我会推荐阅读《Effective Java》,这是关于如何正确使用wait和notify的最好的参考资料。

基于以上认知,下面这个是使用wait和notify函数的规范代码模板:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. // The standard idiom for calling the wait method in Java   
  2. synchronized (sharedObject) {   
  3.     while (condition) {   
  4.     sharedObject.wait();   
  5.         // (Releases lock, and reacquires on wakeup)   
  6.     }   
  7.     // do action based upon condition e.g. take or put into queue   
  8. }  

就像我之前说的一样,在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。如果条件并未改变,wait被调用之前notify的唤醒通知就来了,那么这个线程并不能保证被唤醒,有可能会导致死锁问题。


public class Business {
	private boolean isSub = false;// 设置condition

	public void main() {
		for (int j = 0; j < 50; j++) {
			synchronized (this) {
				while (this.isSub)// 只要是子线程在运行,则一直等待      有时候会出现伪唤醒的情况 比如出现中断等情况
				{
					try {
						this.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int i = 0; i < 10; i++) {
					System.out.print("main:" + i + ",");
				}
				System.out.println();
				this.isSub=true;//让子线程开始执行
				this.notify();
			}
		}
	}

	public void sub() {
		for (int j = 0; j < 50; j++) {
			synchronized (this) {
				while (!this.isSub) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				for (int i = 0; i < 10; i++) {
					System.out.print("sub:" + i + ",");
				}
				System.out.println();
				this.isSub=false;//让主线程开始执行
				this.notify();
			}
		}
	}

	public static void main(String[] args) {
     final Business b=new Business();
      new Thread(
    	new Runnable(){

			@Override
			public void run() {
				b.main();
				
			}
    		
    	}
      ).start();
	new Thread(
	new Runnable(){

		@Override
		public void run() {
			b.sub();
		}		
		
	}
	).start();
	}


}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值