等待线程永远不会醒来



我有一个带有两个线程的线程管理器。一个用于与 GUI 相关的请求,一个用于与测量相关的请求。他们正在运行并检查其请求队列(如果有),则表示它们正在处理请求。可以使用静态ThreadManager.addGuiRequest(eGuiRequest)ThreadManager.addMeasRequest(eMeasRequest)方法随时添加请求。现在两者都需要初始化,这是通过将INIT请求添加到相应的队列来完成的。但是测量的初始化取决于 gui 已经初始化的事实。我试图使用 wait()/notify() 解决这个问题,但我无法让它工作。

这是一个SSCCE。启动时,两个队列都添加了 INIT 请求,然后启动。测量初始化检测到 gui 尚未初始化并执行wait() 。gui 初始化(通过睡眠 5 秒模拟)。这一切都很好用。

gui 初始化后,尝试唤醒测量线程,但测量线程没有唤醒...我的wait()/notify()代码基于这篇文章。这里出了什么问题?

import java.util.LinkedList;
import java.util.NoSuchElementException;
public class ThreadManager {    
    public static void main(String[] args) {
        new ThreadManager();
        ThreadManager.addMeasRequest(eMeasRequest.OTHER_STUFF);
    }
    public enum eGuiRequest { INIT, OTHER_STUFF; }
    public enum eMeasRequest { INIT, OTHER_STUFF; }
    private static LinkedList<eGuiRequest> guiQueue = new LinkedList<eGuiRequest>();
    private static LinkedList<eMeasRequest> measQueue = new LinkedList<eMeasRequest>();
    private static Thread guiThread, measThread;
    protected boolean initialized = false;
    public ThreadManager() {
        final int waitMs = 200;    
        guiThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        if (guiQueue.isEmpty()) sleepMs(waitMs);
                        else {
                            eGuiRequest req = guiQueue.getFirst();
                            processGuiRequest(req);
                            guiQueue.removeFirst();
                        }
                    } catch (NoSuchElementException e) {}
                }
            }
            private void processGuiRequest(eGuiRequest req) {
                System.out.println("T: " + "Processing Gui request: " + req);
                switch (req) {
                case INIT:
                    // do some initializiation here - replaced by a wait:
                    sleepMs(5000);
                    System.out.println("I: " + "guiThread finished, waking up measThread");
                    synchronized (measThread) {
                        initialized = true;
                        measThread.notify();
                    }
                    break;
                case OTHER_STUFF:
                    // do other stuff
                    break;
                }
            }
        });
        measThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        if (measQueue.isEmpty()) sleepMs(waitMs);
                        else {
                            eMeasRequest req = measQueue.getFirst();
                            processMeasurementRequest(req);
                            measQueue.removeFirst();
                        }
                    } catch (NoSuchElementException e) {}
                }
            }
            private void processMeasurementRequest(eMeasRequest req) {
                if (req == eMeasRequest.INIT) { // if init, wait until GUI is initialized
                    synchronized (this) {
                        while (!initialized) {
                            System.out.println("I: " + "measThread waits for guiThread to finish initializiation");
                            try {
                                wait();
                            } catch (Exception e) {}
                            System.out.println("I: " + "measThread awakes");
                        }
                    }
                }
                System.out.println("T: " + "Processing Measurement request: " + req);
                // process request here:
                sleepMs(5000);
            }
        });
        addGuiRequest(eGuiRequest.INIT);
        addMeasRequest(eMeasRequest.INIT);
        guiThread.start();
        measThread.start();
    }
    public static void sleepMs(int ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException ee) {}
    }
    public static void addGuiRequest(eGuiRequest req) {
        guiQueue.add(req);
    }
    public static void addMeasRequest(eMeasRequest req) {
        measQueue.add(req);
    }
}

GUI 线程在 measThread(线程类型)上调用 notify()processMeasurementRequest() 方法在 this 上调用wait(),这是 measThread 使用的 Runnable 实例。

我建议使用两个线程共享的特定对象来等待并通知:

private static final Object GUI_INITIALIZATION_MONITOR = new Object();

此外,我将使用 BlockingQueue,而不是使用 LinkedList 并在请求之间休眠时间:这将允许消耗线程在请求出现请求时立即处理请求,并避免从休眠状态中不必要的唤醒。

此外,您可以使用初始化为 1 的 CountDownLatch 来代替低级别的等待/通知。GUI 线程将在初始化时countDown()闩锁,测量线程将await()闩锁,直到 GUI 线程调用countDown()。这会将复杂的同步和通知内容委托给更高级、经过良好测试的对象。

主要问题是你在measThread上调用notify(),但wait()是在匿名类上调用的。解决此问题的最简单方法是创建一个用于同步的特殊对象。例如,您创建一个字段:

private static final Object LOCK = new Object();

然后,使用此对象编写同步块,并调用其方法,如下所示:

synchronized (LOCK) {
    while (!initialized) LOCK.wait();
}

另外,我不得不说,这段代码根本不对从不同线程访问的字段使用任何同步,这意味着它随时可能中断。这两个队列都在您创建的线程之外访问,这意味着您应该始终使用锁来访问它们,或者您可以使用内置的同步列表使它们线程安全:

quiQueue = Collections.synchronizedList(new LinkedList<eGuiRequest>());

initialized是从同步块访问的,但现在它们在不同的锁上同步(我在回答的开头描述了这个问题)。如果解决此问题,initialized也将正常工作。

只是不要在启动时向测量发送初始化请求。在执行 init gui 请求后从 processGuiRequest() 发送它。然后不需要等待/通知的东西。

最新更新