多线程学习十八:wait和notify

API 介绍

  • obj.wait() 让进入 object 监视器的线程到 waitSet 等待
  • obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
  • obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒
  • wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到被notify 为止
  • wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是未到等待时间被 notify

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。必须获得此对象的锁,才能调用这几个方法
另外:使用wait就是使用了重量级锁

wait notify 原理

在这里插入图片描述

  • Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态
  • BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片
  • BLOCKED 线程会在 Owner 线程释放锁时唤醒
  • WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味者立刻获得锁,仍需进入EntryList 重新竞争
@Slf4j(topic = "c.test17:")
public class Test17 {
    final static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();
        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();
        // 主线程两秒后执行
        Thread.sleep(2);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
            obj.notify(); // 唤醒obj上一个线程
             //obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}
使用notify的其中一种运行结果
20:33:49.136 c.test2: [t1] - 执行....
20:33:49.143 c.test2: [main] - 唤醒 obj 上其它线程
20:33:49.144 c.test2: [t2] - 执行....
20:33:49.144 c.test2: [t1] - 其它代码....
使用notifyAll的其中一种运行结果
20:33:49.136 c.test2: [t1] - 执行....
20:33:49.143 c.test2: [main] - 唤醒 obj 上其它线程
20:33:49.144 c.test2: [t2] - 执行....
20:33:49.144 c.test2: [t1] - 其它代码....
20:33:49.144 c.test2: [t2] - 其它代码....

sleep(long n) 和 wait(long n) 的区别

API 介绍

  • sleep(long n) 和 wait(long n) 的区别
  • sleep 是 Thread 方法,而 wait 是 Object 的方法
  • sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  • sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  • 它们状态 TIMED_WAITING

下面的代码测试:sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁,并且在wait等待过程中,如果未到等待结束时间,其他线程唤醒该线程的时候,会结束wait

@Slf4j(topic = "c.test18:")
public class Test18 {
    final static  Object obj = new Object();
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                log.debug("t1开始执行....");
                synchronized (obj) {
                    try {
                        //Thread.sleep(5000);
                        obj.wait(30000);
                        log.debug("t1执行中....");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug("t1执行完....");
                }
            },"t1").start();
            // 主线程一秒后执行
            Thread.sleep(1000);
            synchronized (obj) {
                log.debug("main执行");
                obj.notify(); //测试wait(timeout)
            }
    }
}

关于sleep(long n) 和 wait(long n) 的实践

例一:
在这个例子中:小南线程不仅要拥有资源而且要有烟才能干活,在没有烟的情况下必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来,而其他线程只需要拥有资源就能干活,但是这时因为小南拥有资源且不释放资源,其他线程只能等着。小南线程加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main没加synchronized(room)就好像main线程是翻窗户进来给小南线程送烟的,才打破这种僵局
解决办法:使用 wait - notify 机制,下面例子2就是实践

public class Test19 {
    static final Object room = new Object(); //相当于共享资源
    static boolean hasCigarette = false;

    public static void main(String[] args) throws InterruptedException {
        //这个线程不仅要拥有资源而且必须有烟才能干活
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("有烟可以开始干活了");
                }
            }
        }, "小南").start();
        //其他线程有共享资源就可以干活
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            }, "其它人").start();
        }
        Thread.sleep(1000);
        new Thread(() -> {
            // 这里能不能加 synchronized (room)?
            hasCigarette = true;
            log.debug("烟到了噢!");
        }, "送烟的").start();
    }
}

例二:
使用wait-notify机制后,小南线程在没有烟的情况下使用wait等待,释放了锁,其他线程可以执行,在他等够2秒的时候发现烟到了,继续干活,这样就解决了了其它干活的线程阻塞的问题,但如果有其它线程也在等待条件呢?请看下面例三

@Slf4j(topic = "c.test20:")
public class Test20 {
    static final Object room = new Object(); //相当于共享资源
    static boolean hasCigarette = false;

    public static void main(String[] args) throws InterruptedException {
        //这个线程不仅要拥有资源而且必须有烟才能干活
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?啊[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                       room.wait(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("有烟可以开始干活了");
                }
            }
        }, "小南").start();
        //其他线程有共享资源就可以干活
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            }, "其它人").start();
        }
        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了噢!");
                room.notify();
            }
        }, "送烟的").start();
    }
}

例三:
在这个例子中:小南线程等待的是烟,而其他线程等待的是外卖,当主线程准备好其中一个资源(烟/外卖)如果使用notify唤醒了不是对应资源的线程,那么导致小南线程和其他线程还是执行不了,因为notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】,因此要使用notifyAll
使用notifyAll仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了,因此要使用用 while + wait,当条件不成立,再次 wait,看下面例四:

@Slf4j(topic = "c.test21:")
public class Test21 {
    static final Object room = new Object(); //相当于共享资源
    static boolean hasCigarette = false;//hasCigarette和hasTakeout都是某一个线程执行所必须的结果
    static boolean hasTakeout = false;

    public static void main(String[] args) throws InterruptedException {
        //这个线程不仅要拥有资源而且必须有烟才能干活
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?啊[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                       room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("有烟可以开始干活了");
                }else {
                    log.debug("没干成活...");
                }
            }
        }, "小南").start();
        //其他线程有共享资源和外卖才可以干活
        new Thread(() -> {
            synchronized (room) {
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("有外卖可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "小女").start();

        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                //room.notify();
                room.notifyAll();
            }
        }, "送外卖").start();
    }
}

例四:

@Slf4j(topic = "c.test21:")
public class Test22 {
    static final Object room = new Object(); //相当于共享资源
    //hasCigarette和hasTakeout都是某一个线程执行所必须的结果
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    public static void main(String[] args) throws InterruptedException {
        //这个线程不仅要拥有资源而且必须有烟才能干活
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?啊[{}]", hasCigarette);
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                       room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("有烟可以开始干活了");
                }else {
                    log.debug("没干成活...");
                }
            }
        }, "小南").start();
        //其他线程有共享资源就可以干活
        new Thread(() -> {
            synchronized (room) {
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("没外卖,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("外卖送到没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("有外卖可以开始干活了");
                } else {
                    log.debug("没干成活...");
                }
            }
        }, "小女").start();
        Thread.sleep(1000);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();
            }
        }, "送外卖").start();
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("烟到了!");
                room.notifyAll();
            }
        }, "送烟").start();
    }
}

通过上面四个例子我们总结出wait-notifyAll的使用模型

synchronized(lock) {
 while(条件不成立) {
 lock.wait();
 }
 // 干活
 }
 //另一个线程
 synchronized(lock) {
 lock.notifyAll();
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java中的waitnotify多线程编程中的两个重要方法,用于线程之间的协作和通信。 wait方法可以使当前线程进入等待状态,直到其他线程调用notifynotifyAll方法唤醒它。在调用wait方法时,当前线程会释放它所持有的锁,以便其他线程可以访问共享资源。 notify方法用于唤醒一个处于等待状态的线程,如果有多个线程在等待,则只会唤醒其中一个线程。notifyAll方法则会唤醒所有处于等待状态的线程。 waitnotify方法必须在同步块中使用,即在使用这两个方法的对象上获取锁。否则会抛出IllegalMonitorStateException异常。 使用waitnotify方法可以实现线程之间的协作和通信,例如生产者消费者模型。在生产者消费者模型中,生产者线程生产数据并将其放入共享队列中,消费者线程从队列中取出数据并进行消费。当队列为空时,消费者线程需要等待生产者线程生产数据,此时可以使用wait方法使消费者线程进入等待状态。当生产者线程生产数据并将其放入队列中时,可以使用notify方法唤醒处于等待状态的消费者线程。 ### 回答2: Java 多线程中的 waitnotify 是两个非常重要的方法,它们可以帮助线程之间达成协作,实现复杂的操作。wait 方法用于让当前线程进入等待状态,直到其他线程通过 notify 方法通知它继续执行。notify 方法则用于唤醒一个等待状态的线程,使其继续执行。 wait 方法 wait 方法用于让当前线程进入等待状态,直到其他线程通过 notifynotifyAll 方法唤醒它。wait 方法需要在 synchronized 代码块中使用,否则会抛出 IllegalMonitorStateException 异常。在进入等待状态之后,线程将释放锁,并且进入一个等待池中,等待其他线程调用 notifynotifyAll 方法唤醒它。 notify 方法 notify 方法用于唤醒一个等待状态的线程,使其继续执行。notify 方法同样需要在 synchronized 代码块中使用,否则同样会抛出 IllegalMonitorStateException 异常。当一个线程调用 notify 方法时,等待池中的线程将会被唤醒,但是它们不能马上继续执行,必须等待当前线程释放锁。如果有多个线程在等待池中,notify 方法只会唤醒其中一个线程,具体唤醒哪个线程是随机的。 notifyAll 方法 notifyAll 方法与 notify 方法类似,但是它会唤醒所有等待池中的线程。notifyAll 方法同样需要在 synchronized 代码块中使用。 使用 waitnotify 实现线程协作 waitnotify 方法可以用来实现线程之间的协作,例如生产者和消费者问题。假设我们有一个共享的队列,生产者向队列中添加数据,消费者从队列中取出数据。如果队列已经满了,生产者就需要等待消费者取走数据,如果队列是空的,消费者就需要等待生产者加入新数据。 在这个问题中,我们可以使用 waitnotify 方法来实现线程之间的协作,代码如下: ``` public class Queue { private final List<Integer> items = new LinkedList<>(); private static final int MAX_SIZE = 10; public synchronized void produce(int item) throws InterruptedException { while (items.size() == MAX_SIZE) { wait(); } items.add(item); notify(); } public synchronized int consume() throws InterruptedException { while (items.isEmpty()) { wait(); } int item = items.remove(0); notify(); return item; } } public class Producer implements Runnable { private final Queue queue; public Producer(Queue queue) { this.queue = queue; } public void run() { for (int i = 0; i < 20; i++) { try { queue.produce(i); System.out.println("Produced: " + i); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public class Consumer implements Runnable { private final Queue queue; public Consumer(Queue queue) { this.queue = queue; } public void run() { for (int i = 0; i < 20; i++) { try { int item = queue.consume(); System.out.println("Consumed: " + item); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } public class Main { public static void main(String[] args) throws InterruptedException { Queue queue = new Queue(); Thread producer = new Thread(new Producer(queue)); Thread consumer = new Thread(new Consumer(queue)); producer.start(); consumer.start(); producer.join(); consumer.join(); } } ``` 在这个示例代码中,我们创建了一个 Queue 类,它有两个方法 produce 和 consume 用于生产和消费数据。在 produce 方法中,我们使用 while 循环来等待队列不满,如果队列已经满了,就调用 wait 方法进入等待状态。在 consume 方法中,我们使用 while 循环来等待队列不空,如果队列是空的,就调用 wait 方法进入等待状态。在生产新数据或者消费数据之后,我们都调用 notify 方法来唤醒等待池中的线程。 最后,我们可以使用 Producer 和 Consumer 类来生产和消费数据,它们分别运行在不同的线程中。在运行这个程序时,生产者将不断生产数据,消费者将不断消费数据,一直到数据生产完毕为止。在这个过程中,生产者和消费者之间通过 waitnotify 方法实现了线程之间的协作。 ### 回答3: Java是一种支持多线程的编程语言,在多线程编程过程中,一个线程可能需要等待另一个线程的某个条件满足后才能继续执行。Java提供了waitnotify来实现线程之间的协作。 wait:使当前线程进入等待状态,释放对象的锁,直到其他线程调用notifynotifyAll方法唤醒它。wait方法必须在持有对象锁的情况下调用,否则会抛出IllegalMonitorStateException异常。 notify:唤醒一个处于等待状态的线程,如果有多个线程等待,则只会唤醒其中一个,具体唤醒哪个线程无法预测。 notifyAll:唤醒所有处于等待状态的线程。 waitnotify必须在同步代码块中调用,并且针对同一个对象。waitnotify的调用顺序也非常重要,如果先调用了notify而没有等待线程,会导致唤醒失效。 在多线程编程中,waitnotify常常用于生产者和消费者模式中的线程之间的通信,生产者线程在生产完毕后调用notify方法唤醒消费者线程来消费数据,消费者线程在消费完毕后调用wait方法等待下一个生产者线程的唤醒。 waitnotify的使用需要谨慎,如果使用不当,会导致死锁或线程饥饿等问题。同时,在Java SE 5之后,Java提供了更加高级的线程库,如ReentrantLock、Condition等,可以更加方便和安全地实现线程之间的协作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值