多线程虚假唤醒 的问题浮现及解决方案

测试代码:

/**
   * 现在两个线程,可以操作初始值为零的一个变量,
    * 实现一个线程对该变量加1,一个线程对该变量减1,交替,来10轮。
   */
public class ThreadTest {

    public static void main(String[] args) {

        Number num = new Number();
        //启动四个线程,循环十次
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    num.plus();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },"a").start();

            new Thread(() -> {
                try {
                    num.reduce();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },"b").start();

            new Thread(() -> {
                try {
                    num.plus();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },"c").start();

            new Thread(() -> {
                try {
                    num.reduce();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },"d").start();
        }

    }
}

//共享资源
class Number {

  int init = 0;
 
  Lock lock = new ReentrantLock();
  Condition condition = lock.newCondition();

    public void plus() {
        //加锁
        lock.lock();
        try {
         //这里会出现虚假唤醒的问题
         if (init != 0) {
            //线程挂起
             condition.await();
        }
        ++ init;
        System.out.println(Thread.currentThread().getName() + ":" + this.init);
        //唤醒线程
        condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void reduce() {

        lock.lock();
        try {

        if (init == 0) {
            condition.await();
        }
        -- init;
        System.out.println(Thread.currentThread().getName() + ":" + this.init);
        condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

执行结果:

a:1
b:0
c:1
d:0
a:1
b:0
c:1
d:0
c:1
d:0
b:-1
b:-2
d:-3
c:-2
a:-1
a:0

结果分析:

执行后发现线程没有按照预想交替打印,参考jdk Api

A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one:
     synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }

 

发现wait( ) 方法会存在虚假唤醒, 这里给出了解决方案把wait() 放入循环中 就可以避免虚假唤醒的问题。

 public void plus() {
        //加锁
        lock.lock();
        try {
         //这里会出现虚假唤醒的问题
         while     (init != 0) {
            //线程挂起
             condition.await();
        }
        ++ init;
        System.out.println(Thread.currentThread().getName() + ":" + this.init);
        //唤醒线程
        condition.signalAll();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

原理分析:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

独行客-编码爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值