Java中的等待和notifyAll死锁 - 调用notifyAll时线程不会唤醒

时间:2015-05-25 18:50:19

标签: java multithreading

我的程序应该按顺序打印出1到10的数字,使用线程(为了学习线程)。 问题是该程序陷入僵局。那是为什么?

我创建了10个这样的主题:

for (int i = 0; i < 10; i++) {
    new PrintThread(i).start();
}

线程类如下所示:

class PrintThread extends Thread {
    int curr;
    static Integer prev;

    PrintThread(int curr) {
        this.curr = curr;
    }

    public synchronized void run() {
        if (prev == null) prev = curr - 1;

        while (curr != prev + 1) {

            System.out.println("Waiting...");

            try { wait(); }
            catch (InterruptedException e){ }

            System.out.println("Woke up!");
        }

        System.out.println(i);
        prev = curr;
        notifyAll();
    }
}

输出

0

Waiting... (9 times)

5 个答案:

答案 0 :(得分:1)

所有线程同步并等待自己。结果,即使一个线程通知,该通知也不会到达任何人,因为其他线程正在等待不同的监视器对象(即它们自己)。在这种情况下,所有线程都应该在公共监视器对象上进行同步和等待/通知。

您根本不应在wait个对象上使用notifyThread,因为它实际上可能会导致线程调度iirc出现死锁。

作为旁注:不要扩展Thread,而是实现Runnable并提供Runnable的实例作为Thread的构造函数的参数。

答案 1 :(得分:0)

@SotiriosDelimanolis的评论清楚地解释了这个问题。想要指出你的问题的另一种方法,检查以下程序,其中一个使用蛮力逻辑乘以时间&amp;其他人在不使用wait的情况下等待与你的逻辑类似的逻辑。

import java.util.Set;        
public class ThreadChecker {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new PrintThread2(i+1).start();
        }
    }

}

//Brute force method
class PrintThread extends Thread {
    int curr;
    static Integer prev;

    PrintThread(int curr) {
        this.curr = curr;
    }

    public synchronized void run() {
        try {
            Thread.sleep(curr*100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(curr);
    }
}

//Logic using Thread.sleep
class PrintThread2 extends Thread {
    int curr;
    static Integer prev=0;

    PrintThread2(int curr) {
        this.curr = curr;
    }

    public synchronized void run() {
        while(curr!=prev && curr-prev!=1){
            try {                   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

         System.out.println(curr);
         prev = curr;      
         //Following is to debug the thread state
         Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
         for(Thread str: threadSet){
            System.out.println("Thread name:"+str.getName()+
                               " ; State:"+str.getState().toString());
         }
    }
}

答案 2 :(得分:0)

wait(),notify()和notifyAll()是Object类的方法。这应该由线程之间共享的对象调用。在这里,我写了一个小型的生产者 - 消费者计划,可以清除你对wait()&amp ;;通知()。

package com.mytest.example;

public class ThreadDemo {

    int message;
    String threadName;
    boolean consumed;

    ThreadDemo(int message, String threadName, boolean consumed) {
        this.setMessage(message);
        this.setThreadName(threadName);
        this.setConsumed(consumed);
    }

    public static void main(String[] args) {
        ThreadDemo demo = new ThreadDemo(0,"Main",true);

        Thread producer = new Thread(new Producer(demo));
        Thread consumer = new Thread(new Consumer(demo));

        producer.start();
        consumer.start();

        try {
            consumer.join();
        } catch (InterruptedException e) {
            System.out.println("ERROR:- "+e.getMessage());
        }
        System.out.println("Main() exit");
    }

    /**
     * @return the message
     */
    public int getMessage() {
        return message;
    }

    /**
     * @param message the message to set
     */
    public void setMessage(int message) {
        this.message = message;
    }

    /**
     * @return the threadName
     */
    public String getThreadName() {
        return threadName;
    }

    /**
     * @param threadName the threadName to set
     */
    public void setThreadName(String threadName) {
        this.threadName = threadName;
    }

    /**
     * @return the contentAvailable
     */
    public boolean isConsumed() {
        return consumed;
    }

    /**
     * @param contentAvailable the contentAvailable to set
     */
    public void setConsumed(boolean consumed) {
        this.consumed = consumed;
    }
}

class Producer implements Runnable {

    ThreadDemo demo;

    Producer(ThreadDemo demo) {
        this.demo = demo;
    }

    @Override
    public void run() {
        for(int i=1; i<=10; i++) {
            produce(i);
        }
    }

    /**
     * This method is used to produce a value in demo object & block the other threads which are waiting for demo object.
     * Once the producer finishes its task, it notifies all other waiting threads & releases the lock on demo object.
     * @param value
     */
    public void produce(int value) {
        synchronized (demo) {
            try {
                while(!demo.isConsumed()) {
                    demo.wait();
                }
                if(demo.isConsumed()) {
                    demo.setMessage(value);
                    demo.setThreadName("Producer");
                    demo.setConsumed(false);
                    System.out.println(demo.getThreadName()+": "+demo.getMessage());
                    Thread.sleep(1000);
                    demo.notifyAll();
                }
            } catch(InterruptedException e) {
                System.out.println("ERROR:- "+e.getMessage());
            }
        }
    }
}

class Consumer implements Runnable {

    ThreadDemo demo;

    Consumer(ThreadDemo demo) {
        this.demo = demo;
    }

    @Override
    public void run() {
        for(int i=1; i<=10;i++) {
            consume();
        }
    }

    /**
     * This method checks whether any content available to consume from producer. If yes then it will acquire the lock 
     * on demo object and consumes the content. Once the content is consumed, it will releases the lock on demo object.
     */
    public void consume() {
        synchronized (demo) {
            try {
                while(demo.isConsumed()) {
                    demo.wait();
                }
                if(!demo.isConsumed()) {
                    demo.setThreadName("Consumer");
                    demo.setConsumed(true);
                    System.out.println(demo.getThreadName()+": "+demo.getMessage());
                    Thread.sleep(1000);
                    demo.notifyAll();
                }
            } catch(InterruptedException e) {
                System.out.println("ERROR:- "+e.getMessage());
            }
        }
    }
}

答案 3 :(得分:0)

原因可能是,根据Thread类而言,除了在特定时间间隔内执行加入保证之外,没有其他方法。 因此,在不同的时间间隔调用不同的线程,这是基于线程调度程序,我们不再完全控制这些方法。

为了更加精确,我建议观看以下视频以了解有关线程的更多信息。

https://www.youtube.com/watch?v=O_Ojfq-OIpM

希望您在观看这三个视频后得到答案

答案 4 :(得分:0)

我试图做空。

import java.util.concurrent.atomic.AtomicInteger;

public class PrintThread implements Runnable {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new PrintThread(i)).start();
        }
    }

    private static final AtomicInteger nextToPrint = new AtomicInteger(0);  
    private final int curr;

    public PrintThread(int curr) {
        this.curr= curr;
    }

    @Override
    public void run() {
        synchronized (nextToPrint) {
            while (nextToPrint.get() != curr) {
                try {
                    nextToPrint.wait();
                } catch (InterruptedException e) { /*do nothing*/ }
            }

            System.out.println(curr);   
            nextToPrint.incrementAndGet();
            nextToPrint.notifyAll();
        }
    }
}