zoukankan      html  css  js  c++  java
  • Java 多线程基础(六)线程等待与唤醒

     Java 多线程基础(六)线程等待与唤醒

    遇到这样一个场景,当某线程里面的逻辑需要等待异步处理结果返回后才能继续执行。或者说想要把一个异步的操作封装成一个同步的过程。这里就用到了线程等待唤醒机制。

    wait()、notify()、notifyAll() 等方法介绍

    在 Object 中,定义了 wait()、notify() 和 notifyAll() 等接口。wait() 的作用是让当前线程进入等待状态,同时,wait() 也会让当前线程释放它所持有的锁。而 notify() 和 notifyAll() 的作用,则是唤醒当前对象上的等待线程;notify() 是唤醒单个线程,而 notifyAll() 是唤醒所有的线程。

    Object类中关于等待/唤醒的API详细信息如下:

    notify()                                       -- 唤醒在此对象监视器上等待的单个线程。
    notifyAll()                                  -- 唤醒在此对象监视器上等待的所有线程。
    wait()                                         -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
    wait(long timeout)                    -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
    wait(long timeout, int nanos)  -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

    二、wait() 和 notify() 示例

    public class Demo02 {
        public static void main(String[] args) {
            Thread t1 = new MyThread("t1");
            synchronized (t1) {
                try {
                    // 启动“线程t1”
                    System.out.println(Thread.currentThread().getName()+" start t1");
                    t1.start();
                    // 主线程等待t1通过notify()唤醒。
                    System.out.println(Thread.currentThread().getName()+" wait()");
                    t1.wait();
                    System.out.println(Thread.currentThread().getName()+" continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            synchronized (this) {
                try {
                    System.out.println(Thread.currentThread().getName()+" call notify()");
                    notify(); // 唤醒当前的Demo02线程
                }catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 运行结果
    main start t1
    main wait()
    t1 call notify()
    main continue

    说明:

    ①、 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表Demo02中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
    ②、“主线程”通过 new ThreadA("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
    ③、“主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
    ④、“线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
    ⑤、“线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。

    具体过程图解

    三、wait(long timeout) 和 notify()

    public class Demo02 {
        public static void main(String[] args) {
            Thread t1 = new MyThread("t1");
    
            synchronized(t1) {
                try {
                    // 启动线程t1
                    System.out.println(Thread.currentThread().getName() + " start t1");
                    t1.start();
    
                    // 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3s延时;然后才被唤醒。
                    System.out.println(Thread.currentThread().getName() + " call wait ");
                    t1.wait(3000);
    
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
        }
    }
    class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        public void run() {
            System.out.println(Thread.currentThread().getName() + " run ");
            // 死循环,不断运行。
            while(true)
                ;
        }
    }
    // 运行结果
    main start t1
    main call wait 
    t1 run             // 3秒后输出 main continue
    main continue 

     说明:

    如下图,说明了“主线程”和“线程t1”的流程。
    ①、注意,图中"主线程" 代表线程main。"线程t1" 代表MyThread中启动的线程t1。 而“锁” 代表“t1这个对象的同步锁”。
    ②、主线程main执行t1.start()启动“线程t1”。
    ③、主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
    ④、“线程t1”运行之后,进入了死循环,一直不断的运行。
    ⑤、超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。

     具体过程图解:

    四、wait() 和 notifyAll()

    public class Demo02 {
        private static Object obj = new Object();
        public static void main(String[] args) {
            MyThread t1 = new MyThread("t1");
            MyThread t2 = new MyThread("t2");
            MyThread t3 = new MyThread("t3");
            t1.start();
            t2.start();
            t3.start();
            try {
                System.out.println(Thread.currentThread().getName()+" sleep(5000)");
                Thread.sleep(5000); // 休眠5秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName()+" notifyAll()");
                obj.notifyAll();
            }
            
        }
        static class MyThread extends Thread{
            public MyThread(String name) {
                super(name);
            }
            public void run() {
                synchronized (obj) { 
                    try {
                        System.out.println(Thread.currentThread().getName() + " run ");
                        obj.wait();
                        System.out.println(Thread.currentThread().getName() + " continue");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    // 运行结果
    t1 run 
    t2 run 
    main sleep(5000)
    t3 run 
    main notifyAll()
    t3 continue
    t2 continue
    t1 continue

    说明:

    ①、 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
    ②、主线程通过sleep(5000)休眠5秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
    ③、主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!

    具体过程图解

    五、 为什么notify(), wait()等函数定义在Object中,而不是Thread中

    Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。

    wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
    OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

    负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

    总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

  • 相关阅读:
    linux下配置java环境及问题
    Chrome工具使用
    Ibatis的resultMap和查询数据的对应关系
    spring mvc接收数组
    PowderDesign的使用
    Android Runtime Stats
    [原创]ASM动态修改JAVA函数之函数字节码初探
    [原创]WB Android客户端架构总结:发WB工作队列设计
    [原创]Android Studio的Instant Run(即时安装)原理分析和源码浅析
    [原创]Android系统中常用JAVA类源码浅析之HashMap
  • 原文地址:https://www.cnblogs.com/lingq/p/13030573.html
Copyright © 2011-2022 走看看