先上代码:
package com.jorchi.thr;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
/**
* notify 和 notifyAll
* notify :只唤醒一个等待线程中的线程
* notifyAll: 唤醒所有等待线程中的线程
*
*/
public class TestNotifyNotifyAll {
/** 锁对象 */
// private static Object LOCK = new Object();
/** 锁对象 - 这里放一个队列 */
static LinkedList LOCK = new LinkedList<Long>();
public static void main(String[] args) {
// 消费者线程
Thread a1 = new Thread(new ConsumerThread(LOCK));
Thread a2 = new Thread(new ConsumerThread(LOCK));
Thread a3 = new Thread(new ConsumerThread(LOCK));
/*Thread a4 = new Thread(new RunnableImplA(LOCK));
Thread a5 = new Thread(new RunnableImplA(LOCK));*/
a1.start();
a2.start();
a3.start();
/*a4.start();
a5.start();*/
// 等线程 A 都跑起来
try {Thread.sleep(5000); } catch(Throwable t){}
// 生产者线程
Thread b1 = new Thread(new ProducerThread(LOCK));
b1.start();
}
}
/**
* 演示用于消费者
*/
class ConsumerThread implements Runnable {
/** 其实跟 TestNotifyNotifyAll.LOCK 是同一个对象 */
private Object obj;
public ConsumerThread(Object obj) {
this.obj = obj;
}
public void run() {
System.out.println("run on RunnableImplA "+new Date());
// synchronized 对象锁,同一时刻最多只有一个线程可以获取到该对象锁
synchronized (obj) {
System.out.println("RunnableImplA 拿到对象锁,1秒后将处于等待状态,线程名称:"+Thread.currentThread().getName()+" "+new Date());
try {
Thread.sleep(1000);
// 发现库存不足
if (TestNotifyNotifyAll.LOCK.isEmpty()) {
// 调用 wait,进入对象等待池,线程处于等待状态
obj.wait();
System.out.println("RunnableImplA 线程被唤醒且拿到对象锁,线程名称:" + Thread.currentThread().getName() + " " + new Date());
}
System.out.println("RunnableImplA 进行消费,1秒后才释放对象锁,线程名称:" + Thread.currentThread().getName() + " " + new Date());
Thread.sleep(1000);
if (TestNotifyNotifyAll.LOCK.isEmpty()) {
System.err.println("逻辑处理错误!!!队列为空如何消费?");
return;
}
System.out.println(TestNotifyNotifyAll.LOCK.removeFirst());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("RunnableImplA 拿到对象锁,释放对象锁,线程名称:"+Thread.currentThread().getName()+" "+new Date());
}
}
}
/**
* 演示用于生产者
*/
class ProducerThread implements Runnable {
/** 其实跟 TestNotifyNotifyAll.LOCK 是同一个对象 */
private Object lockObj;
public ProducerThread(Object obj) {
this.lockObj = obj;
}
public void run() {
System.out.println("run on RunnableImplB");
// 一次生产一个
doProductOne();
doProductOne();
doProductOne();
System.out.println("OK.");
}
/**
* 一次生产一个
*/
private void doProductOne() {
try {
System.out.println("RunnableImplB 正在生产中,预计4秒后生产完成 ...");
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockObj) {
// 已经拿到对象锁,把生产的数据放入队列
TestNotifyNotifyAll.LOCK.add(System.currentTimeMillis());
// notify :只唤醒一个等待线程中的线程
System.out.println("RunnableImplB 已生产完成,并且已经拿到对象锁,call notify "+new Date());
lockObj.notify();
// notifyAll: 唤醒所有等待线程中的线程
/*System.out.println("RunnableImplB 已生产完成,并且已经拿到对象锁,call notifyAll 唤醒所有等待线程中的线程"+new Date());
lockObj.notifyAll();*/
// System.out.println("RunnableImplB 为了演示,2秒后才释放对象锁"+new Date());
// try {Thread.sleep(2000); } catch (Throwable e){}
// notify() 和 notifyAll() 本线程都不会释放它自己的对象锁,
// 直到该synchronized包裹的方法执行完以后,才会释放锁
System.out.println("RunnableImplB 释放对象锁"+new Date());
}
}
}
notify :只唤醒一个等待线程中的线程
notifyAll: 唤醒所有等待线程中的线程
读懂并运行上面的例子,再来理解下面这些技术性解释
先解释两个概念。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。
锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待
然后再来说notify和notifyAll的区别
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象