导航菜单
路很长,又很短
博主信息
昵   称:Cocodroid ->关于我
Q     Q:2531075716
博文数:290
阅读量:551216
访问量:51478
至今:
×
云标签 标签球>>
云标签 - Su的技术博客
博文->>首页 博主的更多博文>>
wait为什么需要放在while内而不能放在if内
Tags : java,锁,多线程,wait发表时间: 2018-07-29 22:52:48
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。
比如: 转自:Su的技术博客  原文地址:

    在学习多线程的时候,是一定会遇到wait的。那么这里有一个疑惑?为什么wait一定得放在while内?不能放在if内呢?为了探究是为什么,这里写了个demo来验证下。

    1、使用if来判断临界条件    

/**
 * @author Cocodroid
 * @create 2018-07-29 21:52
 */
public class WaitNotifyThread {

    private Queue<Integer> queue = new LinkedList<>();

    public void put(Integer data) {
        System.out.println("----put----");
        synchronized (this) {
            System.out.println("----put synchronized----");
            if (queue.size() >= 2) { // 临界条件用if判断
                try {
                    System.out.println("----put wait begin----");
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("----put wait end----");
            }
            queue.offer(data);
            System.out.println("----put data----");
            notifyAll();
            System.out.println("----put notifyAll----");
        }
    }

    public Integer get() {
        System.out.println("----get----");
        synchronized (this) {
            System.out.println("----get synchronized----");
            if (queue.isEmpty()) {  // 临界条件用if判断
                System.out.println("----get wait begin----");
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("----get wait end----");
            }
            System.out.println("----get data----");
            return queue.remove();
        }
    }

    public static void main(String[] args) {
        WaitNotifyThread waitNotifyThread = new WaitNotifyThread();
        Random random = new Random();
        new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(random.nextInt(20));
                    waitNotifyThread.put(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(random.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                waitNotifyThread.get();
            }
        }).start();

        new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(random.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                waitNotifyThread.get();
            }
        }).start();


        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }
}

    出现问题:

----get----
----get synchronized----
----get wait begin----
----get----
----get synchronized----
----get wait begin----
Exception in thread "Thread-2" java.util.NoSuchElementException
	at java.util.LinkedList.removeFirst(LinkedList.java:270)
	at java.util.LinkedList.remove(LinkedList.java:685)
	at com.verysu.study.thread.WaitNotifyThread.get(WaitNotifyThread.java:49)
	at com.verysu.study.thread.WaitNotifyThread.lambda$main$2(WaitNotifyThread.java:85)
	at java.lang.Thread.run(Thread.java:748)
----put----
----put synchronized----
----put data----
----put notifyAll----
----get wait end----
----get data----
----get wait end----
----get data----
----get----
----get synchronized----
----get wait begin----
----put----
----put synchronized----
----put data----
----put notifyAll----
----get wait end----
----get data----
     2、用while替换

        正常,不会打出异常!

    

    3、分析

    使用while能在wait重新被唤醒时再次判断临界条件是否能满足,而用if的话,则唤醒之后就会继续往下执行,也就是说只会判断一次临界条件,假设线程都进入等待队列,这时其它线程执行notifyAll之后,等待队列的线程都会被唤醒,这时使用if的方式由于没能再次检查临界条件,就会往下继续执行,也就是demo里面queue.remove,由于这时队列queue里只有一个资源,但是有两个线程进行取出,就会造成一个线程拿不到资源,这时就爆异常了。使用while的话由于会再次检查临界条件,所以能避免这个问题。

    这里我之前还有个疑问,就是当线程被wait之后进入等待队列,当被唤醒时,线程是继续往wait下面继续执行还是从synchronized monitor锁的地方再次执行?从这个demo也能验证这个问题。【wait被唤醒后,会继续往wait之后的代码执行而不是重新从monitor同步块处执行。】



   

    1)使用wait时切忌要使用在while循环内

    2)wait被唤醒后,会继续往wait之后的代码执行而不是重新从monitor同步块处执行。

   

    参考:https://blog.csdn.net/yiifaa/article/details/76341707



打赏
打赏
关注公众号
公众号
类别:Java| 阅读(196)| 赞 (0)
评论
暂无评论!
发表评论
昵  称:

验证码:

内  容:

    同时赞一个 赞