java多线程wait()方法必须放在while循环里面的原因探析

1、写一个包子生产消费案例:一次生产或消费一个包子,有包子就消费,没有就生产。(部分代码参考传智播客刘意2015Java基础视频讲义)

1.1 写一个Baozi.class,包含main()方法,用来测试

package com.oy.demo3;

/*
 * 包子生产消费案例:一次生产或消费一个包子,有包子就消费,没有就生产。
 */
public class Baozi {
    // 默认是flag,表示没有包子,需要生产线程来生产包子;如果是true,说明有包子,需要消费端来消费包子。
    public boolean flag;

    // 计数,当前在生产或消费第n个包子
    public int count = 0;

    public static void main(String[] args) {
        // 创建共享对象
        Baozi s = new Baozi();

        // 在外界把共享对象创建出来,通过构造方法传递给其他的类。这样st、gt1、gt2就共享s对象。
        SetThread st = new SetThread(s); 
        GetThread gt1 = new GetThread(s);
        GetThread gt2 = new GetThread(s);

        // 线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt1);
        Thread t3 = new Thread(gt2);

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

1.2 生产包子的线程类 SetThread.class

package com.oy.demo3;

public class SetThread implements Runnable {
    private Baozi s;

    public SetThread(Baozi s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                // 判断有没有
                if (s.flag) { // 生产端,有就等待
                    try {
                        System.out.println("生产端:等待。。。");
                        s.wait(); // 等待,并且立即释放锁。将来醒过来的时候,是从这里醒过来的
                        System.out.println("生产端:醒过来了。。。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 开始生产
                s.count++;
                System.out.println("生产第" + s.count + "包子。。。");

                // 生产完后,修改标记为true
                s.flag = true;
                // 唤醒线程
                s.notifyAll();
                System.out.println("==========开始抢CPU的执行权==========");
            }
        }
    }
}

1.3 消费包子的线程类 GetThread.class 

package com.oy.demo3;

public class GetThread implements Runnable {
    private Baozi s;

    public GetThread(Baozi s) {
        this.s = s;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (s) {
                while (!s.flag) { // 消费端,没有就等待
                    try {
                        System.out.println(Thread.currentThread().getName() + "消费端:等待。。。");
                        s.wait(); // 等待,并且立即释放锁。将来醒过来的时候,是从这里醒过来的
                        System.out.println(Thread.currentThread().getName() + "消费端:醒过来了。。。");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 开始消费
                System.out.println(Thread.currentThread().getName() + "消费第" + s.count + "个包子");

                // 消费完了,修改标记为false
                s.flag = false;
                // 唤醒线程
                s.notifyAll();
                System.out.println("==========开始抢CPU的执行权==========");
            }
        }
    }
}

2、测试结果(只选择了控制台打印的部分结果):

==========开始抢CPU的执行权==========
生产端:等待。。。
Thread-2消费端:醒过来了。。。
Thread-2消费第6806个包子
==========开始抢CPU的执行权==========
Thread-2消费端:等待。。。
Thread-1消费端:醒过来了。。。
Thread-1消费端:等待。。。
生产端:醒过来了。。。
生产第6807包子。。。
==========开始抢CPU的执行权==========

3、对测试结果的分析:

  3.1 首先明确,生产端开启了一个线程,消费端开启了两个线程。

  3.2 Thread-2消费第6806个包子,然后唤醒等待的线程;然后3个线程开始抢CPU的执行权,Thread-2消费端线程抢到了,但是此时没有包子了,所以等待;

  3.3 然后,Thread-1消费端线程抢到了执行权,在原来wait()方法的地方醒过来,执行wait()方法后面的代码System.out.println(Thread.currentThread().getName() + "消费端:醒过来了。。。");然后继续while循环判断,由于此时没有包子,所以等待。如果把while改成if,就不会判断是否有包子,之间执行后面的代码消费包子,此时并没有包子了,这就产生了错误(同一个包子被消费了两次)。

  3.4 综上所述:wait()方法套在while循环中,线程下次醒过来后会继续进行循环,判断条件是否满足,满足就重新等待。

  3.5 由于生产端只开启了一个线程,所以将wait()方法套在if代码块中也是可以的,当然使用while也可以。

猜你喜欢

转载自www.cnblogs.com/xy-ouyang/p/10463448.html
今日推荐