【Java基础】wait(), notify(), notifyAll()

函数说明

wait(),notify(),notifyAll()都是java.lang.Object的方法。

  • wait():causes the current thread to wait until another thread invokes the notify() method or notifyAll() method for this object. The thread then waits until it can re-obtain ownership of the monitor and resumes execution. 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
  • notify():wakes up a single thread that is waiting on this object's monitor.如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
  • notifyAll():wakes up all threads that are waiting on this object's monitor;如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

这三个方法,都是Java语言提供的实现线程间阻塞(blocking)和控制进程内调度(inter-process communication)的底层机制。

从功能上来说,wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠,直到有其他线程调用对象的notify唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify就是对对象的唤醒操作。但是需要注意的是notify调用后,并不是马上就释放对象锁的,而是在相应的synchronized{}语句执行块执行结束,自动释放锁后,JVM会在wait对象锁的线程中随机选取一个线程,赋予其对象锁,唤醒线程继续执行

sleep() V.S. wait()

二者都可以暂停当前线程,释放CPU控制权;主要的区别是wait()在释放CPU的同时,释放了对象锁的控制。


说明

  • 正如Java内任何对象都能成为锁(Lock)一样,任何对象也都能成为条件队列(condition queue)。而这个对象里的wait(),notify(),notifyAll()则是这个条件队列的固有的方法。
  • 一个对象的固有锁和它的固有条件队列是相关的,为了调用对象X内条件队列的方法,你必须获得对象X的锁。这是因为等待状态条件的机制和保证状态连续性的机制是紧密的结合在一起的。

首先获得锁再调用函数

因此:在调用wait(),notify()或notifyAll的时候,必须先获得锁,且状态变量须有该锁保护,而固有锁对象与固有条件队列对象又是同一个对象。也就是说,要在某个对象上执行wait,notify,先必须锁定该对象,而对应的状态变量也是由该对象锁保护的。

执行以下代码:

public static void main(String[] args){
    	Object obj = new Object();
    	Object lock = new Object();
    	
    	synchronized(lock){
    		obj.notify();
    	}
    }

public static void main(String[] args){
    	Object obj = new Object();
    	Object lock = new Object();
    	
    	synchronized(lock){
    		try{
    		    obj.wait();
    		}catch(InterruptedException e){
    			e.printStackTrace();
    		}
    	}
    }

上面的两段代码会抛出 java.lang.IllegalMonitorStateException的异常。

分析

如果没有锁,wait和notify有可能会产生竞态条件(race condition)。

考虑一下的生产者和消费者的情景:

1.1 生产者检查条件 - 如果缓存满了,执行1.2

1.2 生产者等待;

2.1 消费者消费了一个单位的缓存,执行2.2

2.2 重新设置条件(如缓存没满),执行2.3

2.3 调用notifyAll()唤醒生产者;

我们希望的正常顺序是: 1.1 - 1.2 - 2.1 - 2.2 - 2.3

但是在多线程的情况下,顺序有可能是: 1.1 - 2.1 - 2.2 - 2.3 - 1.2. 也就是说,在生产者还没有wait之前,消费者就已经notifyAll了,这样的话,生产者就会一直等下去。因此,要解决这个问题,必须在wait和notifyAll的时候,获得该对象的锁,以保证同步。

参考下面的消费者,生产者模型:

public class WaitAndNotifyTest {
    public static final Logger logger = LogManager.getLogger(WaitAndNotifyTest.class.getName());
    
    public static void main(String[] args){
    	QueueBuffer q = new QueueBuffer();
    	
    	new Producer(q);
    	new Consumer(q);
    }
}

class QueueBuffer{
	int n;
	boolean valueSet = false;
	
	public synchronized int get() {
		if (!valueSet){
			try{
				wait();
			}catch (InterruptedException e){
				e.printStackTrace();
			}
		}
		
		System.out.println("Get: " + n);
		valueSet = false;
		notify();
		return n;
	}
	
	public synchronized void put(int n){
		if (valueSet){
			try{
				wait();
			}catch (InterruptedException e){
				e.printStackTrace();
			}
		}
		
		this.n = n;
		valueSet = true;
		System.out.println("Put: " + n);
		notify();
	}
}

class Producer implements Runnable{
	private QueueBuffer q;
	
	public Producer(QueueBuffer q){
		this.q = q;
		new Thread(this, "Producer").start();
	}
	
	public void run() {
		int i = 0;
		while (true){
			q.put(i++);
		}
	}
}

class Consumer implements Runnable{
	private QueueBuffer q;
	
	public Consumer(QueueBuffer q){
		this.q = q;
		new Thread(this, "Consumer").start();
	}
	
	public void run(){
		while (true){
			q.get();
		}
	}
}


示例

示例1 - 交替打印1,2

交替输出1,2,1,2,... ...

public class WaitAndNotifyTest {
    public static final Logger logger = LogManager.getLogger(WaitAndNotifyTest.class.getName());
    
    public static void main(String[] args){
    	final Object lock = new Object();
    	
    	Thread thread1 = new Thread(new OutputThread(1, lock));
    	Thread thread2 = new Thread(new OutputThread(2, lock));
    	
    	thread1.start();
    	thread2.start();
    }
}

class OutputThread implements Runnable {
	private int num;
	private Object lock;
	
	public OutputThread(int num, Object lock){
		super();
		this.num = num;
		this.lock = lock;
	}
	
	public void run() {
		try{
			while(true){
				synchronized (lock){
					lock.notifyAll();
					lock.wait();
					System.out.println(num);
				}
			}
		}catch (InterruptedException e){
			e.printStackTrace();
		}
	}
}

示例2 - 交替打印A,B,C

public class WaitAndNotifyTest implements Runnable {
    public static final Logger logger = LogManager.getLogger(WaitAndNotifyTest.class.getName());
    
    private String name;
    private Object pre;
    private Object self;
    
    public WaitAndNotifyTest(String name, Object pre, Object self){
    	this.name = name;
    	this.pre = pre;
    	this.self = self;
    }
    
    public void run(){
    	int count = 10;
    	
    	while (count > 0){
    		synchronized (pre){
    			synchronized (self) {
    				System.out.println(name);
    				count--;
    				try{
    					Thread.sleep(1);
    				}catch (InterruptedException e){
    					e.printStackTrace();
    				}
    				
    				self.notify();
    			}
    			try{
    				pre.wait();
    			}catch (InterruptedException e){
    				e.printStackTrace();
    			}
    		}
    	}
    }
    public static void main(String[] args) throws Exception {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        
        WaitAndNotifyTest pa = new WaitAndNotifyTest("A", c ,a);
        WaitAndNotifyTest pb = new WaitAndNotifyTest("B", a ,b);
        WaitAndNotifyTest pc = new WaitAndNotifyTest("C", b ,c);
        
        new Thread(pa).start();
        Thread.sleep(10);
        new Thread(pb).start();
        Thread.sleep(10);
        new Thread(pc).start();
        Thread.sleep(10);
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值