Java如何正确的使用wait-notify方法你知道吗

目录
  • 1.sleep(longn)和wait(longn)的区别
  • 2.正确使用wait-notify方法[while(条件)+wait]
    • 2.1问题1
    • 2.2问题2
    • 2.3问题3
    • 2.4问题4
    • 2.5最终结果
  • 总结

问题:

sleep() 和 wait() 有什么区别?

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

(1) sleep 是 Thread 方法,而 wait 是 Object 的方法 ;

(2) sleep 不需要强制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用 ;

(3) sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁 ;

(4) 它们 状态 TIMED_WAITING;

@Slf4j
public class Test1 {
    private static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                log.debug("t1线程获得锁....");
                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();
        // 等待1s,让线程去执行t1线程
        Thread.sleep(1000);
        // 获取不到锁,因为t1线程在睡眠期间并不会释放锁
        synchronized (lock){
            log.debug("主线程想要获取锁....");
        }
    }
}

执行sleep()方法后的结果:在线程t1睡眠期间,主线程没有获得锁

10:34:34.574 [t1] DEBUG com.example.test.Test1 - t1线程获得锁....

@Slf4j
public class Test1 {
    private static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                log.debug("t1线程获得锁....");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();
        // 等待1s,让线程去执行t1线程
        Thread.sleep(1000);
        // 获取不到锁,因为t1线程在睡眠期间并不会释放锁
        synchronized (lock){
            log.debug("主线程想要获取锁....");
        }
    }
}

执行wait()方法后的结果:线程t1等待20s,在线程t1等待期间,主线程获得了锁

10:36:22.723 [t1] DEBUG com.example.test.Test1 - t1线程获得锁....
10:36:23.721 [main] DEBUG com.example.test.Test1 - 主线程想要获取锁....

2. 正确使用wait-notify方法 [while(条件)+wait]

场景:有几个小孩都想进入房间内使用算盘(CPU)进行计算,老王(操作系统)就使用了一把锁(synchronized)让同一时间只有一个小孩能进入房间使用算盘,于是他们排队进入房间。

(1) 小南最先获取到了锁,进入到房间内,但是由于条件不满足(没烟干不了活),小南不能继续进行计算 ,但小南如果一直占用着锁,其它人就得一直阻塞,效率太低。

(2) 于是老王单开了一间休息室(调用 wait 方法),让小南到休息室(WaitSet)等着去了,这时锁释放开, 其它人可以由老王随机安排进屋

(3) 直到小M将烟送来,大叫一声 [ 你的烟到了 ] (调用 notify 方法)

(4) 小南于是可以离开休息室,重新进入竞争锁的队列

下面我们看如何正确的实现这个场景

2.1 问题1

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有烟,默认没有
    private 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("有烟,[{}],可以开始干活了", hasCigarette);
                }
            }
        },"小南").start();
        // 其他5个线程也想获取锁进入房间
        for(int i=0;i<5;i++){
            new Thread(()->{
                synchronized (room){
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }
        // 主线程等待1s
        Thread.sleep(1000);
        // 因为小南线程使用sleep()方法,因此他在睡眠期间并不释放锁,送烟的没办法拿到锁进入房间送烟
        new Thread(()->{
            synchronized (room){
                hasCigarette = true;
            }
        },"送烟的").start();
    }
}

执行结果:

11:10:50.556 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:10:50.565 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:10:52.565 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了

(1) 小南线程在睡眠期间并不释放锁,因此其他线程线程也没办法获取到锁进入房间,送烟线程就没办法送烟;

(2) 其它干活的线程,都要一直阻塞,效率太低 ;

要解决上述的问题,需要使用wait-notify机制

2.2 问题2

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有烟,默认没有
    private 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();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if(hasCigarette){
                    log.debug("有烟,[{}],可以开始干活了", hasCigarette);
                }
            }
        },"小南").start();

        // 其他5个线程也想获取锁进入房间
        for(int i=0;i<5;i++){
            new Thread(()->{
                synchronized (room){
                    log.debug("可以开始干活了");
                }
            },"其他人").start();
        }

        // 主线程等待1s
        Thread.sleep(1000);

        // 送烟的,唤醒正在睡眠的小南线程
        new Thread(()->{
            synchronized (room){
                hasCigarette = true;
                room.notify();
            }
        },"送烟的").start();
    }
}

执行结果:

11:21:36.775 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:21:36.780 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以开始干活了
11:21:37.773 [小南] DEBUG com.example.test.Test2 - 有烟没?[true]
11:21:37.774 [小南] DEBUG com.example.test.Test2 - 有烟,[true],可以开始干活了

解决了其他线程阻塞问题,但是如果有其他线程也在等待呢?就是说等待的线程不止小南一个,那么会不会唤醒错了呢?

2.3 问题3

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有烟,默认没有
    private static boolean hasCigarette = false;
    // 是否有外卖,默认没有
    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("有烟,[{}],可以开始干活了", hasCigarette);
                }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("有外卖,[{}],可以开始干活了", hasTakeout);
                }else{
                    log.debug("没干成活..");
                }
            }
        },"小女").start();
        // 主线程等待1s
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外卖到了....");
                room.notify();
            }
        },"送外卖的").start();
    }
}

执行结果:送外卖的应该叫醒小女但是却把小南叫醒了

11:31:50.989 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:31:50.994 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 有外卖没?[false]
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 没外卖,先歇会!
11:31:51.987 [送外卖的] DEBUG com.example.test.Test2 - 外卖到了....
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 没干成活..

notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】

2.4 问题4

解决方法:改为 notifyAll

new Thread(()->{
    synchronized (room){
        hasTakeout = true;
        log.debug("外卖到了....");
        room.notifyAll();
    }
},"送外卖的").start();

执行结果:

11:34:24.789 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:34:24.798 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:34:24.798 [小女] DEBUG com.example.test.Test2 - 有外卖没?[false]
11:34:24.802 [小女] DEBUG com.example.test.Test2 - 没外卖,先歇会!
11:34:25.794 [送外卖的] DEBUG com.example.test.Test2 - 外卖到了....
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外卖没?[true]
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外卖,[true],可以开始干活了
11:34:25.794 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:34:25.795 [小南] DEBUG com.example.test.Test2 - 没干成活..

从结果可以看出小女干成活,小南没有干成活。既然送烟的没到,小南应该继续等待才行,等送烟的来了再干活。

用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了

2.5 最终结果

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有烟,默认没有
    private 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("有烟,[{}],可以开始干活了", hasCigarette);
                }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("有外卖,[{}],可以开始干活了", hasTakeout);
                }else{
                    log.debug("没干成活..");
                }
            }
        },"小女").start();
        // 主线程等待1s
        Thread.sleep(1000);
        // 送烟的,唤醒正在睡眠的小南线程
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外卖到了....");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}
@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有烟,默认没有
    private 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("有烟,[{}],可以开始干活了", hasCigarette);
                }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("有外卖,[{}],可以开始干活了", hasTakeout);
                }else{
                    log.debug("没干成活..");
                }
            }
        },"小女").start();
        // 主线程等待1s
        Thread.sleep(1000);
        // 送烟的,唤醒正在睡眠的小南线程
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外卖到了....");
                room.notifyAll();
            }
        },"送外卖的").start();
    }
}

执行结果:当没烟的时候,小南线程继续等待,等待下一次判断有烟的时候再干活

11:38:36.206 [小南] DEBUG com.example.test.Test2 - 有烟没?[false]
11:38:36.212 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 有外卖没?[false]
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 没外卖,先歇会!
11:38:37.205 [送外卖的] DEBUG com.example.test.Test2 - 外卖到了....
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外卖没?[true]
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外卖,[true],可以开始干活了
11:38:37.205 [小南] DEBUG com.example.test.Test2 - 没烟,先歇会!

使用wait-notify的正确姿势:

synchronized(lock) {
     while(条件不成立) {
         lock.wait();
     }
}
//另一个线程
synchronized(lock) {
 	lock.notifyAll();
}

调用wait()和notify()系列方法进行线程通信的要点如下:

(1) 调用某个同步对象locko的wait()和notify()类型方法前,必须要取得这个锁对象的监视锁,所以wait()和notify()类型方法必须放在synchronized(locko)同步块中,如果没有获得监视锁,JVM就会报IllegalMonitorStateException异常。

(2) 调用wait()方法时使用while进行条件判断,如果是在某种条件下进行等待,对条件的判断就不能使用if语句做一次性判断,而是使用while循环进行反复判断。只有这样才能在线程被唤醒后继续检查wait的条件,并在条件没有满足的情况下继续等待。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 详解Java程序并发的Wait-Notify机制

    Wait-Notify场景 典型的Wait-Notify场景一般与以下两个内容相关: 1. 状态变量(State Variable) 当线程需要wait的时候,总是因为一些条件得不到满足导致的.例如往队列里填充数据,当队列元素已经满时,线程就需要wait停止运行.当队列元素有空缺时,再继续自己的执行. 2. 条件断言(Condition Predicate) 当线程确定是否进入wait或者是从notify醒来的时候是否继续往下执行,大部分都要测试状态条件是否满足.例如,往队列里添加元素,队列已满

  • Java使用wait() notify()方法操作共享资源详解

    Java多个线程共享资源: 1)wait().notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写. 2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁,或者叫管程) 3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程: 4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线

  • Java 中Object的wait() notify() notifyAll()方法使用

    Java 中Object的wait() notify() notifyAll()方法使用 一.前言 对于并发编程而言,除了Thread以外,对Object对象的wati和notify对象也应该深入了解其用法,虽然知识点不多. 二.线程安全基本知识 首先应该记住以下基本点,先背下来也无妨: 同一时间一个锁只能被一个线程持有 调用对象的wait()和notify()前必须持有它 三.wait()和notify()理解 3.1 wait()和notify()方法简介 wait()和notify()都是

  • Java通过wait()和notifyAll()方法实现线程间通信

    本文实例为大家分享了Java实现线程间通信的具体代码,供大家参考,具体内容如下 Java代码(使用了2个内部类): package Threads; import java.util.LinkedList; /** * Created by Frank */ public class ProdCons { protected LinkedList<Object> list = new LinkedList<>(); protected int max; protected bool

  • Java如何正确的使用wait-notify方法你知道吗

    目录 1.sleep(longn)和wait(longn)的区别 2.正确使用wait-notify方法[while(条件)+wait] 2.1问题1 2.2问题2 2.3问题3 2.4问题4 2.5最终结果 总结 问题: sleep() 和 wait() 有什么区别? 1. sleep(long n) 和 wait(long n) 的区别 (1) sleep 是 Thread 方法,而 wait 是 Object 的方法 ; (2) sleep 不需要强制和 synchronized 配合使用

  • Java多线程wait()和notify()方法详细图解

    目录 一.线程间等待与唤醒机制 二.等待方法wait() 三.唤醒方法notify() 四.关于wait和notify内部等待问题(重要) 五.完整代码(仅供测试用) 总结 一.线程间等待与唤醒机制 wait()和notify()是Object类的方法,用于线程的等待与唤醒,必须搭配synchronized 锁来使用. 多线程并发的场景下,有时需要某些线程先执行,这些线程执行结束后其他线程再继续执行. 比如: 一个长跑比赛,裁判员要等跑步运动员冲线了才能宣判比赛结束,那裁判员线程就得等待所有的运

  • 详解Java中异步转同步的六种方法

    目录 一.问题 应用场景 二.分析 三.实现方法 1.轮询与休眠重试机制 2.wait/notify 3.Lock Condition 4.CountDownLatch 5.CyclicBarrier 6.LockSupport 一.问题 应用场景 应用中通过框架发送异步命令时,不能立刻返回命令的执行结果,而是异步返回命令的执行结果. 那么,问题来了,针对应用中这种异步调用,能不能像同步调用一样立刻获取到命令的执行结果,如何实现异步转同步? 二.分析 首先,解释下同步和异步 同步,就是发出一个调

  • 浅谈Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.先来我们来看下相关定义: wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁. wait(long):超时等待最多long毫秒,如果没有通知就超时返回. notify() :通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提

  • java使用spring实现发送mail的方法

    本文实例讲述了java使用spring实现发送mail的方法.分享给大家供大家参考.具体如下: 这里借鉴别人的优点以及自己的一些加工,写出如下代码: package test; import java.util.Properties; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import org.springframework.mail.SimpleMailMessage; imp

  • Java线程池的几种实现方法及常见问题解答

    工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能.所以,"池"的用处就凸显出来了. 1. 为什么要使用线程池 在3.6.1节介绍的实现方式中,对每个客户都分配一个新的工作线程.当工作线程与客户通信结束,这个线程就被销毁.这种实现方式有以下不足之处: •服务器创建和销毁工作的开销( 包括所花费的时间和系统资源 )很大.这一项不用解释,可以

  • Java数据库连接池的几种配置方法(以MySQL数据库为例)

    一.Tomcat配置数据源: 前提:需要将连接MySQL数据库驱动jar包放进Tomcat安装目录中common文件夹下的lib目录中 1.方法一:在WebRoot下面建文件夹META-INF,里面建一个文件context.xml,如下: <?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/chaoshi" auth="

  • java 避免出现NullPointerException(空指针)的方法总结

    java 避免出现NullPointerException(空指针)的方法总结 Java应用中抛出的空指针异常是解决空指针的最好方式,也是写出能顺利工作的健壮程序的关键.俗话说"预防胜于治疗",对于这么令人讨厌的空指针异常,这句话也是成立的.值得庆幸的是运用一些防御性的编码技巧,跟踪应用中多个部分之间的联系,你可以将Java中的空指针异常控制在一个很好的水平上.顺便说一句,这是Javarevisited上的第二个空指针异常的帖子.在上个帖子中我们讨论了Java中导致空指针异常的常见原因

  • 浅析Java类和数据结构中常用的方法

    1.Object类里面常用的方法: protected Object clone()创建并返回此对象的一个副本. boolean equals(Object obj)指示其他某个对象是否与此对象"相等". protected void finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法. Class<?> getClass()返回此 Object 的运行时类. int hashCode()返回该对象的哈希码值. void notif

随机推荐