Java生产者和消费者多线程问题

下面是一个线程安全的消费者、生产者实现,i 为共享变量,i 为 1 时消费,i 为 0 时生产;
但写完我有两个问题:

1. 在本地运行时,为什么使用 this.notify() 会出现运行一段时间就卡住了,像死锁一样。而用this.notifyAll() 就没有问题?
2. 为什么主线程没有使用join、sleep的方法,在启动完其他线程后变为阻塞状态,而不是结束?

// 多线程环境下的生产者和消费者
public class ThreadTest {

    volatile Integer i = 0;

    private synchronized void add() {
        while (i > 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("add: " + ++i);
        // TODO 问题1:这里用 this.notify() 运行一会就卡住,像死锁一样,不知道为什么?
        this.notifyAll();
    }

    private synchronized void reduce() {
        while (i < 1) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("red: " + --i);
         // TODO 问题1:这里用 this.notify() 运行一会就卡住,像死锁一样,不知道为什么?
        this.notifyAll();
    }


    public static void main(String[] args) throws InterruptedException {
        ThreadTest test = new ThreadTest();

        Runnable reduceR = () -> {
            while (true) {
                test.reduce();
            }
        };
        Thread reduceT1 = new Thread(reduceR);
        Thread reduceT2 = new Thread(reduceR);
        Thread reduceT3 = new Thread(reduceR);

        Runnable addR = () -> {
            while (true) {
                test.add();
            }
        };
        Thread addT1 = new Thread(addR);

        reduceT1.start();
        reduceT2.start();
        reduceT3.start();
        addT1.start();
        // TODO 问题2:主线程没有用sleep、join等方法, 应该到这就结束了,但实际中没有,而是阻塞状态,为什么?
    }

}

notify 方法,调用一次只能唤醒一个线程,但是,楼主开启了4个线程,当 reduce 线程调用 notify 唤醒一个线程时,很有可能唤醒的是另外的reduce线程,而不是 add 线程,这时,另外的 reduce 线程进入自旋循环中,继续等等 i 的变化,不会进行额外的 notify 操作。

如果上面的描述可以理解的话:

判断 i 的值是否满足条件,为什么使用 while 自旋循环,而不使用 if 语句来进行判断?
如果你理解了为什么使用 while 循环,就不难理解为什么要使用 notifyAll 方法进行唤醒了。
提示一下,wait方法在被调用时,会阻塞当前线程,同时释放锁,造成synchronized形成的临界区可以被其他线程进入。
然后,进一步分析,就可以得到答案了。

问题1:notify 和 notifyAll 的区别楼上已经说了;
问题2:所有非守护线程退出后进程才自动结束,你的这个例子main方法执行完了,但是创建的4个都是非守护线程,得等这几个线程都执行完了进程才会退出;
如果把这4个线程设置为守护线程,才会在主线程执行完后立即退出
试试这样:


 reduceT1.setDaemon(true);
        reduceT2.setDaemon(true);
        reduceT3.setDaemon(true);
        addT1.setDaemon(true);

我用以下代码,参考运行结果,想通了问题1

// 多线程环境下的生产者和消费者
public class ThreadTest {

    volatile Integer i = 0;

    private synchronized void add() {
        while (i > 0) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 生产后: " + ++i);
        this.notify();
    }

    private synchronized void reduce() {
        while (i < 1) {
            try {
                System.out.println(Thread.currentThread().getName() + " wait");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 消费后: " + --i);
        this.notify();
    }


    public static void main(String[] args) throws InterruptedException {
        ThreadTest test = new ThreadTest();

        Runnable reduceR = () -> {
            while (true) {
                test.reduce();
            }
        };
        Thread reduceT1 = new Thread(reduceR);
        reduceT1.setName("消费者1");
        Thread reduceT2 = new Thread(reduceR);
        reduceT2.setName("消费者2");
        Thread reduceT3 = new Thread(reduceR);
        reduceT3.setName("消费者3");

        Runnable addR = () -> {
            while (true) {
                test.add();
            }
        };
        Thread addT1 = new Thread(addR);
        addT1.setName("生产者1");

        Thread addT2 = new Thread(addR);
        addT2.setName("生产者2");

        reduceT1.start();
        reduceT2.start();
        reduceT3.start();
        addT1.start();
        addT2.start();
    }

}
  • 运行结果:(产生死锁)
消费者1 wait
消费者2 wait
消费者3 wait
生产者2 生产后: 1
生产者2 wait
生产者1 wait
消费者1 消费后: 0
消费者1 wait
消费者2 wait
---------------   tip:(产生死锁了) ------------------------