美文网首页
虚假唤醒

虚假唤醒

作者: 因你而在_caiyq | 来源:发表于2019-05-15 22:47 被阅读0次

原创文章,转载请注明原文章地址,谢谢!

生产者消费者案例

我们先用经典的生产者消费者案例来引出问题。

//产品
public class Clerk {

    private int product = 0;

    //生产产品
    public synchronized void get() {
        if (product >= 10) {
            System.out.println("产品已满!");
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + ++product);
        }
    }

    //销售产品
    public synchronized void sale() {
        if (product <= 0) {
            System.out.println("产品售空!");
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + --product);
        }
    }
}
//生产者
public class Producer implements Runnable {

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.get();
        }
    }
}
//消费者
public class Consumer implements Runnable {

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}
//测试
public class ProducerAndConsumerForLockTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);
        new Thread(producer, "生产者 A").start();
        new Thread(consumer, "消费者 B").start();
    }
}

测试结果

生产者 A : 1
生产者 A : 2
生产者 A : 3
生产者 A : 4
生产者 A : 5
生产者 A : 6
生产者 A : 7
生产者 A : 8
生产者 A : 9
生产者 A : 10
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
产品已满!
消费者 B : 9
消费者 B : 8
消费者 B : 7
消费者 B : 6
消费者 B : 5
消费者 B : 4
消费者 B : 3
消费者 B : 2
消费者 B : 1
消费者 B : 0
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!
产品售空!

通过上述案例,我们发现,在生产产品达到上限的时候,还在不断的生产,而在销售产品已经售空的情况下,依然在不停的销售产品,那么此时就出现了问题。那如何解决这样的问题呢?这就要使用到下面的等待唤醒机制。

等待唤醒机制

通过上面的案例,我们在这里引入等待唤醒机制。所谓等待唤醒机制,就是当一个线程在执行了某一个操作的时候,将其进入等待状态,并释放锁,其他线程执行完指定的操作后,再将其唤醒。

public class Clerk {

    private int product = 0;

    //生产产品
    public synchronized void get() {
        if (product >= 1) {
            System.out.println("产品已满!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + ++product);
            this.notifyAll();
        }
    }

    //销售产品
    public synchronized void sale() {
        if (product <= 0) {
            System.out.println("产品售空!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + " : " + --product);
            this.notifyAll();
        }
    }
}

测试结果

产品售空!
生产者 A : 1
产品已满!
消费者 B : 0
产品售空!
生产者 A : 1
产品已满!
消费者 B : 0
产品售空!
生产者 A : 1
产品已满!
消费者 B : 0
......

此时从结果中可以看出,还算是比较和谐的一个结果。但是我发现一个问题,就是我在运行多次的时候,出现了不一样的结果,其中有一种情况就是,程序一直没有停止,我们来演示一下这个现象。

//生产者
public class Producer implements Runnable {

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.get();
        }
    }
}

程序运行结果发现,程序确实一直没有停止,那这是什么原因呢?这里的改动只不过是将生产者中的run方法,阻塞了200ms。

我们假设现在产品为0,消费者剩下最后一次循环,生产者剩下2次循环。

  • 消费者抢到锁,执行sale方法,此时产品售空,然后执行wait方法,消费者线程被挂起,并释放锁。
  • 生产者循环次数为2,消费者剩余循环次数为0,产品数量为0。
  • 生产者拿到锁,执行get方法,生产产品,并执行notifyAll方法,唤醒消费者线程。
  • 生产者循环次数为1,消费者剩余循环次数为0,产品数量为1。
  • 此时消费者线程被唤醒,这里要注意,被唤醒之后,它是从上次被挂起的地方,继续往下执行,而在本程序中,往下执行将不会进行其他操作,消费者线程结束。
  • 生产者拿到锁,执行get方法,此时产品已满,然后执行wait方法,生产者线程被挂起,并释放锁。但是此时消费者线程已经结束,将再也没有线程来唤醒生产者线程,所以程序一直处于运行状态。

其实解决这个问题也很容易,我们将程序中的else语句去掉即可。

虚假唤醒问题

上面的案例,我们使用等待唤醒机制,似乎已经解决了问题,但是我们在上述案例中的测试中,只是使用了两个线程,那如果我们使用多个线程,会不会出现问题呢?

public class ProducerAndConsumerForLockTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);
        new Thread(producer, "生产者 A").start();
        new Thread(consumer, "消费者 B").start();
        new Thread(producer, "生产者 C").start();
        new Thread(consumer, "消费者 D").start();
    }
}

测试结果

产品售空!
产品售空!
生产者 A : 1
产品已满!
消费者 D : 0
产品售空!
消费者 B : -1
产品售空!
消费者 D : -2
产品售空!
......

我们已经发现,已经出现了负数,显然已经出现了问题。我们来简单分析一下原因。其实很好理解,当两个消费者线程同时执行sale方法时,产品售空,那么都将执行wait方法,处于挂起等待状态,并释放锁,然后生产者拿到锁,生产产品,执行notifyAll方法,唤醒了所有消费者线程,那么当第一个消费者执行了消费以后,第二个消费者又进行消费,此时便出现了负数,出现了问题。像这样的情况,就叫做虚假唤醒。
那么如何解决这个问题呢?我们只需要将判断的if换成while即可。换句话说,为了避免虚假唤醒问题,应该将判断一直使用在循环中。

//产品
public class Clerk {

    private int product = 0;

    //生产产品
    public synchronized void get() {
        while (product >= 1) {
            System.out.println("产品已满!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + ++product);
        this.notifyAll();
    }

    //销售产品
    public synchronized void sale() {
        while (product <= 0) {
            System.out.println("产品售空!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " : " + --product);
        this.notifyAll();
    }
}

测试结果

生产者 A : 1
产品已满!
产品已满!
消费者 D : 0
产品售空!
产品售空!
生产者 C : 1
产品已满!
产品已满!
消费者 B : 0
产品售空!
产品售空!
......

博客内容仅供自已学习以及学习过程的记录,如有侵权,请联系我删除,谢谢!

相关文章

  • 虚假唤醒

    原创文章,转载请注明原文章地址,谢谢! 生产者消费者案例 我们先用经典的生产者消费者案例来引出问题。 测试结果 通...

  • 线程虚假唤醒

    一般而言线程调用wait()方法后,需要其他线程调用notify,notifyAll方法后,线程才会从wait方法...

  • Futex 初学之 虚假唤醒

    用while 代替 if ,防止虚假唤醒。 while( valueneed to wait == true){ ...

  • 虚假唤醒(spurious wakeup)

    On a multi-processor, it may be impossible for an impleme...

  • 线程虚假唤醒的Java演示

    什么是线程虚假唤醒 在不同的语言,甚至不同的操作系统上,条件锁都会产生虚假唤醒现象。所有语言的条件锁库都推荐用户把...

  • Java虚假唤醒(如何避免)

    什么是假唤醒? 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功1.比如说...

  • 多线程学习(十二)

    一.补充 1.1)虚假唤醒:即wait被唤醒,共享数据队列中没有数据,比如连续多次notify_one()等操作会...

  • 线程通信中的虚假唤醒

    今天做了一个关于线程通信的虚假唤醒题。做的时候没想到,对此也不是很熟悉,查过资料才知道原因,在此自己归纳总结一遍。...

  • JUC线程高级---线程控制通信Condition

    **版权声明:本文为小斑马伟原创文章,转载请注明出处! 为了避免虚假唤醒问题,使用while循环,取消if els...

  • 自旋锁在高并发的用处,实现各种锁

    虚假唤醒:由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。这就是所...

网友评论

      本文标题:虚假唤醒

      本文链接:https://www.haomeiwen.com/subject/eedkoqtx.html