java多线程并发环境下为什么使用while而不用if

目录

前言

一 . 使用if所引发的问题

1.1 虚假唤醒

 1.2 虚假唤醒代码示例

 1.3 解决虚假唤醒

二 . 为什么用while就能解决问题

结论


前言

再开始本文之前 , 先给大家看一张图 , 这是Object.wait()的源码介绍 , 翻译过来内容如下

        使当前线程等待另一个线程调用此对象的notify()方法或notifyAll()方法.  换句话说 , 这个方法就像他只是调用 wait(0) 一样 .

        当前线程必须拥有此对象的监视器。线程释放此监视器的所有权,并等待另一个线程通过调用notify方法或notifyAll方法通知等待此对象监视器的线程唤醒。然后,线程等待,直到它可以重新获得监视器的所有权并恢复执行。

        注意红框圈住部分 , 说的是 中断和虚假环境也是可能的 , 所以这种方法应该是重在循环内 ,  至于为什么说 请继续往下看. 

        

一 . 使用if所引发的问题

1.1 虚假唤醒

        虚假唤醒这种情况只会出现在多线程环境中,指的是在多线程环境下,多个线程等待在同一个条件上,等到条件满足时,所有等待的线程都被唤醒,

        当一定的条件触发时会唤醒很多在阻塞态的线程 , 其中一部分线程从条件变量中苏醒过来时,发现等待的条件并没有满足 ,  这便是虚假唤醒

 1.2 虚假唤醒代码示例

我们在使用线程时,进行条件判断时,往往会先考虑使用if进行判断,在线程进行等待时就会出现不确定的结果。先来看看两个线程下的操作。

首先创建一个模拟业务类, 也是一个经典案例 "生产者&消费者" 

public class MyService {
    private int num = 1;

    /**
     * 模拟上架货物 , 始终保证有货
     */
    @SneakyThrows
    public synchronized void plus() {
        // 当货物数量大于0 , 不缺货 , 不用补 , 使当前线程休眠
        if (num > 0) {
            System.out.println("num = " + num + " plus线程休眠");
            // wait() 使当线程停在此处,  直到notify() 或者 notifyAll()被唤醒后 , 接着向下执行
            this.wait();
            System.out.println("num = " + num + " plus线程被唤醒");
        }
        // num 自加(补货
        num++;
        // 补货完毕 , 唤醒等待当前对象的其他线程前来竞争锁
        this.notify();
        System.out.println("当前num == " + num);
    }

    /**
     * 模拟消费货物
     */
    @SneakyThrows
    public synchronized void sub() {
        // 当货物数量num <= 0 时说明没货了 , 线程休眠
        if (num <= 0) {
            System.out.println("num = " + num + " sub线程休眠");
            this.wait();
            System.out.println("num = " + num + " sub线程被唤醒");
        }
        // num 自减
        num--;
        // 唤醒等待当前对象的其他线程来竞争锁
        this.notify();
        System.out.println("当前num == " + num);
    }
}

然后分别创建补货/售货两个线程 , 用来模拟后续的动作

// 补货线程
public class PlusThread implements Runnable {

    private MyService myService;

    public PlusThread(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            myService.plus();
        }
    }
}

// 售货线程
public class SubThread implements Runnable {

    private MyService myService;

    public SubThread(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            myService.sub();
        }
    }
}

接下来创建一个测试类 , 用来模拟多线程下的生产/消费 

/**
 * @author hxk
 * @version test: ThreadTest01.java, v 0.1 2022-06-30 10:13 hxk Exp $
 */
public class ThreadTest01 {
    @SneakyThrows
    public static void main(String[] args) {
        // new 一个业务类
        MyService myService = new MyService();
        // 补货线程
        PlusThread plusThread = new PlusThread(myService);
        // 售货线程
        SubThread subThread = new SubThread(myService);
        
        // 然后一个补货 , 一个售货 
        Thread p = new Thread(plusThread);
        p.start();
        Thread s = new Thread(subThread);
        s.start();
    }
}

        执行main方法,结果如下图 .

  这样执行得到的结果是正常的 , 暂时也看不出问题 , 可是当我们在此基础之上 , 将售货的线程再增加一个 , 问题就暴露出来了 , main方法中追加一下代码 , 再去执行看下效果

        Thread s = new Thread(subThread);
        s.start();

 连续执行几次后发现 , 其中几次执行结果出现了 负值 , 为了方便大家理解个中缘由 , 我给所有线程都加上名字 , 便于大家理解 .

 

 然后这里重新执行 几次 , 得到错误的日志入下 , 我将图中的几个关键点标记了起来 

 首先是关键点1  , 这一步是属于正常唤醒 , num = 0 了 , p0线程需要重新补货, 所以p0分别执行了num++ 和 , notify() 唤醒等待此对象的其他线程 

然后就到了关键点2 , 这时候售货线程s1被唤醒 , 对num进行num-- , 同样的再执行notify() , 来唤醒等待该对象的其他线程 ,

这时候最关键的关键点3 , 本应该被唤醒的是p0 , 结果却唤醒了s0 , 而s0被唤醒的位置至关重要 , 那就是wait()函数后面, 注意这时候 已经处在 If 条件内了 , 就不在进行判断了 , 而是继续向下执行下去了 ,  再一次 num--  , 这便出现了最开始看到的负数, 也就是虚假唤醒(线程从条件变量中苏醒过来时,发现等待的条件并没有满足) 

 1.3 解决虚假唤醒

要想解决虚假唤醒这个问题 , 其实在wait()的源码备注中就有给出解决方案, 同时也有说明 中断和虚假环境也是可能的 , 所以这种方法应该是重在循环内 ,

 改良后代码 

public class MyService {
    private int num = 1;

    /**
     * 模拟上架货物 , 始终保证有货
     */
    @SneakyThrows
    public synchronized void plus() {
        // 当货物数量大于0 , 不缺货 , 不用补 , 使当前线程休眠 , 改良后 此处判断变为了 while
        while (num > 0) {
            System.out.println("num = " + num + ", "  + Thread.currentThread().getName() +  "线程休眠");
            // wait() 使当线程停在此处,  直到notify() 或者 notifyAll()被唤醒后 , 接着向下执行
            this.wait();
            System.out.println("num = " + num + ", "  + Thread.currentThread().getName() +  "线程被唤醒");
        }
        // num 自加(补货
        num++;
        // 补货完毕 , 唤醒等待当前对象的其他线程前来竞争锁
        this.notify();
        System.out.println("当前num == " + num);
    }

    /**
     * 模拟消费货物
     */
    @SneakyThrows
    public synchronized void sub() {
        // 当货物数量num <= 0 时说明没货了 , 线程休眠 改良后 此处判断变为了 while
        while (num <= 0) {
            System.out.println("num = " + num + ", "  + Thread.currentThread().getName() +  "线程休眠");
            this.wait();
            System.out.println("num = " + num + ", "  + Thread.currentThread().getName() +  "线程被唤醒");
        }
        // num 自减
        num--;
        // 唤醒等待当前对象的其他线程来竞争锁
        this.notify();
        System.out.println("当前num == " + num);
    

二 . 为什么用while就能解决问题

首先我们先来回顾一下java 基础 里面  if 和while 的区别  ,

结论

        一个被唤醒的线程就处于就绪状态了,就可以等待被cpu调度了,

        如果用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码.   

        而使用while虽然也会从wait之后的代码开始运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

        所以必须用while来检查,这样可以保证每次被唤醒都会检查一次条件。

        

 

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值