Java 线程在 join() 中被阻塞

Java Thread is blocked in join()

我有以下简单代码,我在其中放入和取出表示为 ArrayList 的队列。

public class EmailService {

    private Queue<Email> emailQueue;
    private Object lock;
    private volatile boolean run;
    private Thread thread;

    public void sendNotificationEmail(Email email) throws InterruptedException {
        emailQueue.add(email);

        synchronized (lock) {
            lock.notify();
            lock.wait();
        }
    }

    public EmailService() {
        lock = new Object();
        emailQueue = new Queue<>();
        run = true;
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (run) {
                    System.out.println("ruuuning");
                    synchronized (lock) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (emailQueue.getSize() > 0) {
                            sendEmail(emailQueue.poll());
                        }
                        lock.notify();
                    }
                }
            }
            private void sendEmail(Email email) {
                System.out.println("Sent email from " + email.getFrom() + " to " + email.getTo() + " with content: " + email.getContent());
            }
        });
        thread.start();
    }

    public void close() throws InterruptedException {
        run = false;
        synchronized (lock) {
            lock.notify();
            System.out.println("Thread will join " + thread.isInterrupted());
            thread.join();
            System.out.println("Thread after join");
        }
    }
}

我不明白为什么我的线程在 join() 方法中被阻塞。 从 main 我调用如下:

eService = new EmailService();
Email e1 = new Email(client1, client2, "content1");
eService.sendNotificationEmail(e1);
eService.close();

@drekbour 解释了您的程序如何在 join() 调用中挂起,但仅供参考:这是您的程序可能挂起的 不同 方式。这称为丢失通知。

您的主线程创建了一个新的 EmailService 实例。新实例创建它的线程并调用 thread.start() *但是*它 可能 需要一些时间让线程真正启动 运行。同时...

您的主线程创建一个新的 Email 实例,并调用 eService.sendNotificationEmail(...)。该函数将新消息添加到队列,锁定 lock,通知锁,然后等待锁。

最后,服务线程启动,进入它的run()方法,锁上锁,然后调用lock.wait().

此时程序会卡住,因为每个线程都在等待对方的通知。


避免丢失通知的方法是,在消费者线程中,如果您正在等待的事情已经发生,则不要调用wait()

synchronized(lock) {
    while (theThingHasNotHappenedYet()) {
        lock.wait();
    }
    dealWithTheThing();
}

在生产者线程中:

    synchronized(lock) {
        makeTheThingHappen();
        lock.notify();
    }

注意两个线程是如何锁定锁的。有没有想过为什么 lock.wait() 如果锁没有被锁定会抛出异常?上面的例子说明了原因。锁可以防止生产者线程在消费者已经决定等待之后让事情发生。这很关键:如果消费者在生产者调用 notify() 之后等待,那么游戏就结束了。程序挂了。

没有 运行宁它...

  • close() 方法在调用 thread.join() 时保持 lock 并等待 thread(永远)
  • thread 正在等待重新获取 lock,所以不能 运行

现在双方都在互相等待,这是一个deadlock。尝试将 Thread.join() 移动到 synchronized 块之后:

    public void close() throws InterruptedException {
        run = false;
        synchronized (lock) {
            lock.notify();
            System.out.println("Thread will join " + thread.isInterrupted());
        }
        thread.join();
        System.out.println("Thread after join");
    }