4.12多线程--wait/notify

故事引入

image.png

image.png

wait / notify 原理

image.png
  • Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
    WaitSet 里的线程是获取过锁 又放弃了,EntryList 中的是还没有获得锁
  • BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间
  • BLOCKED 线程会在 Owner 线程释放锁时被唤醒
  • WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但并不意味着会立即获得锁,仍然会进入 EntryList 重新竞争

wait / notify API

  • obj.wiat() 让进入 obj 监视器的线程到 WaitSet 等待
  • obj.notify() 选中 obj 监视器的 WaitSet 中的某个线程被唤醒
  • obj.notifyAll() obj 监视器的 WaitSet 中的全部线程都被唤醒
    他们都是线程之间进行协作的手段,都属于 obj 对象的方法。必须获得此对象的锁,才能调用这两个方法。(线程只有变成 obj 对象的监视器的 Owner 才能调用)
/**
  * 1、wait / notify / notifyAll
  * 2、带一个参数的 wait :等待设定时间后,如果没有被其他线程唤醒,就不等了,继续向下执行;如果还没到等待时间,另一个线程来唤醒,就被唤醒,继续向下执行;
  * 3、带两个参数的 wait:wait(毫秒,纳秒),不会精确到纳秒,多一毫秒;
 */
public class BiasedDemo5 {
    final static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                System.out.println("线程 t1 开始执行");
                try {
                    lock.wait();
                    // lock.wait(1000); // 即使没有其他线程唤醒,1s 后 继续执行后续代码
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 t1 完成其他业务");
            }
        },"t1").start();

        new Thread(()->{
            synchronized (lock){
                System.out.println("线程 t2 开始执行");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 t2 完成其他业务");
            }
        },"t2").start();

        Thread.sleep(2000);
        synchronized (lock){
//            lock.notify(); // 只唤醒一个
            lock.notifyAll();// 全部唤醒,全部执行
        }
    }
}

wait / notify 的正确使用姿势(step1-5)

sleep(long n) 和 wait(long n) 的区别
1)sleep 是 Thread 的静态方法,wait 是 Object的方法
2)sleep 不需要强制和 synchronized 配合使用,但 wait 必须和 synchronized 联合使用,获取对象锁
3)如果 sleep 和 synchronized 联合使用,在 sleep 的时候不会释放对象锁,但 wait 在等待是会释放对象锁
4)sleep 和 wait 都不占用 CPU 时间,状态都是 TIMED_WAITING

public class BiasedDemo6 {
    final static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                System.out.println("线程 t1 开始执行");
                try {
//                    lock.wait();
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程 t1 完成其他业务");
            }
        },"t1").start();

        Thread.sleep(1000);
        synchronized (lock){
            System.out.println("主线程获得锁,开始执行");
        }
    }
}
/**
 *  STEP 0(使用 wait / notify 前)
 **/
public class BiasedDemo7 {
    final static Object room = new Object();
    static boolean hasCigarette = false;

    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
                if (!hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 线程[t1], 没有烟");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
                if(hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 线程[t1], 有烟,开始干活");
                }
            }
        },"t1").start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    System.out.println(sdf.format(new Date()) + " 线程[其他],开始干活");
                }
            },"其他").start();
        }

        Thread.sleep(1000);
        new Thread(()->{
            // synchronized (room){
                hasCigarette = true;
                System.out.println(sdf.format(new Date()) + " 线程[送烟的],来送烟了");
            // }
        },"送烟的").start();
    }
}

输出:(注意看前边的时间哈)

05:33:58 线程[t1], 有没有烟?false
05:33:58 线程[t1], 没有烟
05:33:59 线程[送烟的],来送烟了
05:34:00 线程[t1], 有没有烟?true
05:34:00 线程[t1], 有烟,开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
05:34:00 线程[其他],开始干活
  • 其他干活的线程,都要一直阻塞,效率太低
  • 线程 t1 必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
  • 加了 synchronized(room) 后,就好比 t1 在里面反锁了门睡觉,烟根本没法送进门,main 线程没加 synchronized 就好像 main 线程是翻窗户进来的
  • 解决方法:使用 wait - notify 机制
/**
 *  STEP 1(使用 wait / notify)
 **/
public class BiasedDemo8 {
    final static Object room = new Object();
    static boolean hasCigarette = false;

    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
                if (!hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 线程[t1], 没有烟");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 线程[t1], 有没有烟?" +hasCigarette);
                if(hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 线程[t1], 有烟,开始干活");
                }
            }
        },"t1").start();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (room){
                    System.out.println(sdf.format(new Date()) + " 线程[其他],开始干活");
                }
            },"其他").start();
        }

        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasCigarette = true;
                System.out.println(sdf.format(new Date()) + " 线程[送烟的],来送烟了");
                room.notify();
            }
        },"送烟的").start();
    }
}

输出

05:51:00 线程[t1], 有没有烟?false
05:51:00 线程[t1], 没有烟
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:00 线程[其他],开始干活
05:51:01 线程[送烟的],来送烟了
05:51:01 线程[t1], 有没有烟?true
05:51:01 线程[t1], 有烟,开始干活
  • 解决了其他线程被阻塞的问题
  • 但如果有其他线程也在等待条件呢?

虚假唤醒:

/**
 *  STEP 2(错误唤醒/虚假唤醒)
 **/
public class BiasedDemo9 {
    final static Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 线程[小南], 有没有烟?" +hasCigarette);
                if (!hasCigarette){ //while (!hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 线程[小南], 没有烟,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 线程[小南], 有没有烟?" +hasCigarette);
                if(hasCigarette){
                    System.out.println(sdf.format(new Date()) + " 线程[小南], 有烟,开始干活");
                }
            }
        },"小南").start();

        new Thread(()->{
            synchronized (room){
                System.out.println(sdf.format(new Date()) + " 线程[小女], 有没有外卖?" +hasTakeout);
                if (!hasTakeout){//while (!hasTakeout){
                    System.out.println(sdf.format(new Date()) + " 线程[小女], 没有外卖,等一会");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(sdf.format(new Date()) + " 线程[小女], 有没有外卖?" +hasTakeout);
                if(hasTakeout){
                    System.out.println(sdf.format(new Date()) + " 线程[小女], 有外卖,开始干活");
                }
            }
        },"小女").start();


        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                System.out.println(sdf.format(new Date()) + " 线程[送货的],来送外卖了");
                room.notify();
                //room.notifyAll();
            }
        },"送货的").start();
    }
}

输出:

06:02:46 线程[小南], 有没有烟?false
06:02:46 线程[小南], 没有烟,等一会
06:02:46 线程[小女], 有没有外卖?false
06:02:46 线程[小女], 没有外卖,等一会
06:02:47 线程[送货的],来送外卖了
06:02:47 线程[小女], 有没有外卖?true
06:02:47 线程[小女], 有外卖,开始干活
06:02:47 线程[小南], 有没有烟?false
//线程1
synchronized(room){
  while(条件不成立){
    room.wait();
  }
  // 干活
}
// 线程2 
synchronized(room){
   room.notifyAll();
}

你可能感兴趣的:(4.12多线程--wait/notify)