线程基础(六)wait() notify() notifyAll()
轮询
线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作。
想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处理?一个办法就是,B线程while(i == 10000),这样两个线程之间就有了通信,B线程不断通过轮训来检测i == 10000这个条件。
这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操作。如果可以把这些轮询的时间释放出来,给别的线程用,就好了。
优化这种情况可以使用等待/通知机制wait/notify
wait/notify
在Object对象中有三个方法wait()、notify()、notifyAll(),既然是Object中的方法,那每个对象自然都是有的。如果不接触多线程的话,这两个方法是不太常见的。下面看一下前两个方法:
1、wait()
wait()的作用是使当前执行代码的线程进行等待,将当前线程置入"预执行队列"中,并且wait()所在的代码处停止执行,直到接到通知或被中断。在调用wait()之前,线程必须获得该对象的锁,因此只能在同步方法/同步代码块中调用wait()方法,然后wait会放掉当前锁。
2、notify()
notify()的作用是,如果有多个线程等待,那么线程规划器随机挑选出一个wait的线程,对其发出通知notify(),并使它等待获取该对象的对象锁。注意"等待获取该对象的对象锁",这意味着,即使收到了通知,wait的线程也不会马上获取对象锁,必须等待notify()方法的线程释放锁才可以。和wait()一样,notify()也要在同步方法/同步代码块中调用。
总结起来就是,wait()使线程停止运行,notify()使停止运行的线程继续运行。而且是在相同的对象锁上才能互相通知,调用等待/通知的对象必须是上锁的对象,否则根本不存在联系,自然无法等待/通知,如果下面两点都没有满足,则抛出java.lang.IllegalMonitorStateException
1.必须有被锁的对象
2.必须是被锁的对象wait/notify
public class WaitNotify {
public static void main(String[] args) {
//创建一个任意对象锁
Object lock = new Object();
//线程1等待
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock){
try {
System.out.println("wait开始"+System.currentTimeMillis()/1000);
lock.wait();
System.out.println("wait结束"+System.currentTimeMillis()/1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//线程2唤醒
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock){
System.out.println("notify开始"+System.currentTimeMillis()/1000);
lock.notify();
System.out.println("notify结束"+System.currentTimeMillis()/1000);
}
}
});
try {
t1.start();
Thread.sleep(3000);
t2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
wait开始1547636343
notify开始1547636346
notify结束1547636346
wait结束1547636346
先创建两个线程 一个等待,一个唤醒,线程1启动后先让main线程休眠3秒,这样确保wait线程1先执行。这样才能看到wait()和notify()的效果
第一行和第二行之间的time减一下很明显就是3s,说明wait()之后代码一直暂停,notify()之后代码才开始运行。
wait()方法可以使调用该线程的方法释放共享资源的锁,然后从运行状态退出,进入等待队列,直到再次被唤醒。
notify()方法可以随机唤醒等待队列中等待同一共享资源的一个线程,并使得该线程退出等待状态,进入可运行状态,而不是立即运行,是等待当前线程notify运行完毕所有锁内代码之后释放完锁,才会执行wait该线程的代码,从上面例子可以看出,notify开始结束是在一起打印的就表示这个点是正确的
notifyAll()方法可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态
最后,如果wait()方法和notify()/notifyAll()方法不在同步方法/同步代码块中被调用,那么虚拟机会抛出java.lang.IllegalMonitorStateException,注意一下。
如果将wait()外的同步代码块屏蔽
wait开始1547636692
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.xuxu.g_wait_notify.WaitNotify$1.run(WaitNotify.java:14)
at java.lang.Thread.run(Thread.java:745)
notify开始1547636695
notify结束1547636695
书中解释
详细的介绍了 线程wait()/notify()的状态及实现机制