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 - 餐厅:生意太好,菜品供不应求,只能等待厨房做菜...当前菜架:[]
...

 

posted @ 2020-10-28 09:56  姚春辉  阅读(236)  评论(0编辑  收藏  举报