Java并发07:Thread的基本方法(4)-Thread.sleep()、Object.wait()、notify()和notifyAll()
本章主要对Java中Thread类的基本方法进行学习。
1.序言
Thread类作为线程的基类,提供了一系列方法,主要有:
- Thread.sleep(long):强制线程睡眠一段时间。
- Thread.activeCount():获取当前程序中存活的线程数。
- thread.start():启动一个线程。
- Thread.currentThread():获取当前正在运行的线程。
- thread.getThreadGroup():获取线程所在线程组。
- thread.getName():获取线程的名字。
- thread.getPriority():获取线程的优先级。
- thread.setName(name):设置线程的名字。
- thread.setPriority(priority):设置线程的优先级。
- thread.isAlive():判断线程是否还存活着。
- thread.isDaemon():判断线程是否是守护线程。
- thread.setDaemon(true):将指定线程设置为守护线程。
- thread.join():在当前线程中加入指定线程,使得这个指定线程等待当前线程,并在当前线程结束前结束。
- thread.yield():使得当前线程退让出CPU资源,把CPU调度机会分配给同样线程优先级的线程。
- thread.interrupt():使得指定线程中断阻塞状态,并将阻塞标志位置为true。
- object.wai()、object.notify()、object.notifyAll():Object类提供的线程等待和线程唤醒方法。
为了便于阅读,将以上所有方法,放在5篇文章中进行学习。
本章主要学习绿色字体标记的方法,其他方法请参加其他章节。
2.Object.wait()与Thread.sleep()
先来看看sleep()的定义与注释:
/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds, subject to * the precision and accuracy of system timers and schedulers. The thread * does not lose ownership of any monitors. * ... */ public static native void sleep(long millis) throws InterruptedException;
说明:
- sleep():属于Thread类的方法。
- sleep():让当前正在运行的线程休眠指定毫秒的时间。
- sleep():休眠的线程并不会失去任何的监视器(可以理解为成锁)。
再来看看wait()方法的定义与注释:
/** * Causes the current thread to wait until another thread invokes the * {@link java.lang.Object#notify()} method or the * {@link java.lang.Object#notifyAll()} method for this object. * In other words, this method behaves exactly as if it simply * performs the call {@code wait(0)}. * <p> * The current thread must own this object's monitor. The thread * releases ownership of this monitor and waits until another thread * notifies threads waiting on this object's monitor to wake up * either through a call to the {@code notify} method or the * {@code notifyAll} method. The thread then waits until it can * re-obtain ownership of the monitor and resumes execution. * <p> * ... */ public final void wait() throws InterruptedException { wait(0); }
说明:
- wait():wait()是Object的方法。
- wait():让当前线程等待,直到另一个线程调用了当前对象上的notify()或者notifyAll()方法。
- wait():在调用时,会释放当前对象的监视器的所有权(可以理解成解锁)。
- 调用wait()类的线程必须拥有这个对象的监视器(可以理解成锁)。
- wait()的线程会一直等待,直到另一线程通知当前对象上的所有线程通过notify()唤醒单个线程或者通过notifyAll()唤醒全部线程。
- wait()也可以只等待一定的时间就自动唤醒,方法是wait(long)。
sleep()与wait()的区别和联系:
- 二者都是让线程暂停运行。
- sleep()不会释放任何锁,wait()会释放对象上的锁。
- sleep()正常恢复的方式只能是等待时间耗尽,wait()除了等待时间耗尽,还可以被其他线程唤醒(notify()和notifyAll)。
3.Object.notify()和Object.notifyAll()
先来看看这Object.notify()的定义和注释:
/** * Wakes up a single thread that is waiting on this object's * monitor. If any threads are waiting on this object, one of them * is chosen to be awakened. The choice is arbitrary and occurs at * the discretion of the implementation. A thread waits on an object's * monitor by calling one of the {@code wait} methods. * <p> * ... */ public final native void notify();
说明:
- notify():唤醒等待对象监视器的单个线程。
- notify():如果等待对象监视器的有多个线程,则选取其中一个线程进行唤醒。
- notify():选择唤醒哪个线程是任意的,由CPU自己决定。
再来看看这Object.notifyAll()的定义和注释:
/** * Wakes up all threads that are waiting on this object's monitor. A * thread waits on an object's monitor by calling one of the * {@code wait} methods. * <p> * ... */ public final native void notifyAll();
说明:
- notifyAll():唤醒等待对象监视器的所有线程。
很显然,notify和notifyAll一个是唤醒单个线程,一个是唤醒所有线程,前提是都在指定的对象监视器上。
4.实例代码与结果
4.1.实例场景
- 这是一个典型的生产者与消费者的示例。
- 程序中有两个类:厨房类和餐厅类。厨房负责炒菜(生产者),餐厅负责卖菜(消费者)。
- 厨房中只有一个厨子,他炒好一道菜之后,需要休息2秒钟。
- 厨房中有菜架子,能够存放炒好的菜,作为储备,以应对生意火爆的饭点时间。
- 菜架子上最多盛放6个盘子。所以,当菜架子上盛满6道菜之后,厨师就可以暂时休息了。
- 餐厅负责卖菜,大概需要花费1.5秒到2.5秒才能卖出一道菜。
- 餐厅会时刻查看菜架子上的菜品数量,当储备的菜品少于2盘时,就通知厨师该继续炒菜了,以免出现供不应求的情况。
- 在饭点时间,虽然厨师一直在炒菜,也可能因为卖菜卖的太快,导致菜架子上一盘菜都没有,这时餐厅只能耐心等待厨师炒菜。
4.2.实现思路
- 这是一个典型的线程等待与唤醒的问题。需要用到synchronized关键字、wait()以及notify()方法。
- wait()以及notify()方法都需要锁定共同的对象,在这个场景中,这个共同的对象就是:菜架子(厨房通过菜架子盛放菜品,餐厅从菜架子获取菜品)。
- 当菜架子上盛满6道菜之后,厨师就可以暂时休息了----这就是调用wait()的时机。
- 当储备的菜品少于2盘时,就通知厨师该继续炒菜了----这就是调用notify()的时机。
4.3.实例代码
关于实例代码的其他说明:
- 将菜架子以队列Queue的形式实现,以FIFO(First in, First out,先进先出)。
代码:
/** * <p>线程基本方法(sleep、wait、notify、notifyAll、synchronized)</p> * * @author hanchao 2018/3/11 14:14 **/ public class ThreadWaitDemo { private static final Logger LOGGER = Logger.getLogger(ThreadWaitDemo.class); /** * 现有菜品 */ private static final Queue<String> FOOD_QUEUE = (Queue<String>) new LinkedList<String>(); /** * <p>菜品工具类</p> **/ public static class Foods { private static String[] foods = new String[]{"[鱼香肉丝]", "[水煮肉片]", "[地三鲜]", "[红烧肉]", "[干煸豆角]"}; /** * <p>随机获取一个菜名</p> * * @author hanchao 2018/3/11 15:46 **/ static String randomFood() { return foods[RandomUtils.nextInt(0, foods.length)]; } } /** * <p>厨房生产各种菜肴(wait、notify、synchronized)</p> **/ static class Kitchen extends Thread { @Override public void run() { while (true) { //加锁 synchronized (FOOD_QUEUE) { //菜架满了,厨房不必再茶菜,等着前厅通着再炒菜 //厨房的菜架能够存放菜品的最大值 int maxSize = 6; if (maxSize == FOOD_QUEUE.size()) { try { LOGGER.info("厨房菜架满了,厨房不必再茶菜,等着前厅通着再炒菜,当前菜架:" + FOOD_QUEUE.toString()); FOOD_QUEUE.wait(111); } catch (InterruptedException e) { e.printStackTrace(); } } else { //炒一个菜 String food = Foods.randomFood(); FOOD_QUEUE.add(food); try { LOGGER.info("厨房炒了一个:" + food + ",厨师歇息2分钟...当前菜架:" + FOOD_QUEUE.toString()); //抄完一个菜,歇息1分钟 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } /** * <p>餐厅消费各种菜肴(wait、notify、synchronized)</p> **/ static class Restaurant extends Thread { @Override public void run() { while (true) { //加锁 synchronized (FOOD_QUEUE) { //如果生意太好,菜品供不应求,只能等待厨房做菜... if (0 == FOOD_QUEUE.size()) { try { LOGGER.info("餐厅:生意太好,菜品供不应求,只能等待厨房做菜...当前菜架:" + FOOD_QUEUE.toString()); FOOD_QUEUE.wait(3000); } catch (InterruptedException e) { e.printStackTrace(); } } else if (FOOD_QUEUE.size() > 0) { //如果有菜,则消费菜品 //当厨房的储备菜品所剩不多时,告诉厨师开始炒菜 //当厨房还剩几个菜时,继续炒菜 int minSize = 2; if (FOOD_QUEUE.size() <= minSize) { FOOD_QUEUE.notify(); LOGGER.info("餐厅:厨房的储备菜品所剩不多时,厨师们该继续炒菜了..."); } //消费菜品 String food = FOOD_QUEUE.poll(); try { //随机一定时间吃掉一道菜 Thread.sleep(RandomUtils.nextInt(1500, 2500)); LOGGER.info("餐厅:刚刚消费了一道" + food + "...当前菜架:" + FOOD_QUEUE.toString()); } catch (InterruptedException e) { e.printStackTrace(); } } } } } } /** * <p>线程基本方法(sleep、wait、notify、notifyAll、synchronized)</p> **/ public static void main(String[] args) throws InterruptedException { //通过关键字synchronized和Object的方法wait()/notify()/notifyAll()实现线程等待与唤醒 //通过object.wait(),使得对象线程进行入等待唤醒状态,并是否对象上的锁 //通过object.notify()/object.notifyALL(),唤醒此对象上等待的线程,并获得对象上的锁 //wait()/notify()/notifyAll()必须在synchronized中使用 new Kitchen().start(); //先让厨房多炒几个菜 Thread.sleep(10000); //餐厅开始消费 new Restaurant().start(); } }
4.4.运行结果
运行结果:
2018-03-12 14:44:56 INFO ThreadWaitDemo:65 - 厨房炒了一个:[水煮肉片],厨师歇息2分钟...当前菜架:[[水煮肉片]] 2018-03-12 14:44:58 INFO ThreadWaitDemo:65 - 厨房炒了一个:[鱼香肉丝],厨师歇息2分钟...当前菜架:[[水煮肉片], [鱼香肉丝]] 2018-03-12 14:45:00 INFO ThreadWaitDemo:65 - 厨房炒了一个:[鱼香肉丝],厨师歇息2分钟...当前菜架:[[水煮肉片], [鱼香肉丝], [鱼香肉丝]] 2018-03-12 14:45:02 INFO ThreadWaitDemo:65 - 厨房炒了一个:[干煸豆角],厨师歇息2分钟...当前菜架:[[水煮肉片], [鱼香肉丝], [鱼香肉丝], [干煸豆角]] 2018-03-12 14:45:04 INFO ThreadWaitDemo:65 - 厨房炒了一个:[干煸豆角],厨师歇息2分钟...当前菜架:[[水煮肉片], [鱼香肉丝], [鱼香肉丝], [干煸豆角], [干煸豆角]] 2018-03-12 14:45:08 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[水煮肉片]...当前菜架:[[鱼香肉丝], [鱼香肉丝], [干煸豆角], [干煸豆角]] 2018-03-12 14:45:10 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[鱼香肉丝]...当前菜架:[[鱼香肉丝], [干煸豆角], [干煸豆角]] 2018-03-12 14:45:11 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[鱼香肉丝]...当前菜架:[[干煸豆角], [干煸豆角]] 2018-03-12 14:45:11 INFO ThreadWaitDemo:99 - 餐厅:厨房的储备菜品所剩不多时,厨师们该继续炒菜了... 2018-03-12 14:45:13 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[干煸豆角]...当前菜架:[[干煸豆角]] 2018-03-12 14:45:13 INFO ThreadWaitDemo:99 - 餐厅:厨房的储备菜品所剩不多时,厨师们该继续炒菜了... 2018-03-12 14:45:15 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[干煸豆角]...当前菜架:[] 2018-03-12 14:45:15 INFO ThreadWaitDemo:90 - 餐厅:生意太好,菜品供不应求,只能等待厨房做菜...当前菜架:[] 2018-03-12 14:45:17 INFO ThreadWaitDemo:65 - 厨房炒了一个:[红烧肉],厨师歇息2分钟...当前菜架:[[红烧肉]] 2018-03-12 14:45:19 INFO ThreadWaitDemo:65 - 厨房炒了一个:[鱼香肉丝],厨师歇息2分钟...当前菜架:[[红烧肉], [鱼香肉丝]] 2018-03-12 14:45:21 INFO ThreadWaitDemo:65 - 厨房炒了一个:[干煸豆角],厨师歇息2分钟...当前菜架:[[红烧肉], [鱼香肉丝], [干煸豆角]] 2018-03-12 14:45:25 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[红烧肉]...当前菜架:[[鱼香肉丝], [干煸豆角]] 2018-03-12 14:45:25 INFO ThreadWaitDemo:99 - 餐厅:厨房的储备菜品所剩不多时,厨师们该继续炒菜了... 2018-03-12 14:45:28 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[鱼香肉丝]...当前菜架:[[干煸豆角]] 2018-03-12 14:45:28 INFO ThreadWaitDemo:99 - 餐厅:厨房的储备菜品所剩不多时,厨师们该继续炒菜了... 2018-03-12 14:45:30 INFO ThreadWaitDemo:106 - 餐厅:刚刚消费了一道[干煸豆角]...当前菜架:[] 2018-03-12 14:45:30 INFO ThreadWaitDemo:90 - 餐厅:生意太好,菜品供不应求,只能等待厨房做菜...当前菜架:[] ...