1

I have a ThreadManager with two Threads. One for gui-relevant requests and one for measurement-relevant requests. The are both running and checking their queue of requests, if there is any, they are processing the request. One can add requests at any time, using the static ThreadManager.addGuiRequest(eGuiRequest) and ThreadManager.addMeasRequest(eMeasRequest) methods. Now both of those need to be initialized which is done by adding a INIT request to the corresponding queue. But the initialization of the measurement is depending on the fact that the gui is already initialized. I tried to solve this using wait()/notify(), but I can not get it working.

Here is a SSCCE. At startup, both queues have a INIT request added and are then started. The measurement initialization detects that the gui is not yet initialized and perfomrs a wait(). The gui initializes (simulated by sleeping for 5s). This all works fine.

After the gui initialized, it tries to wake up the measurement thread, but the measurement thread does not wake up... I based my wait()/notify() code on this article. What is going wrong here?

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);
    }
}
4

3 回答 3

3

GUI 线程调用notify()on measThread(类型为 Thread),processMeasurementRequest()方法调用wait()on this,这是由measThread.

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

private static final Object GUI_INITIALIZATION_MONITOR = new Object();

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

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

于 2012-06-20T09:16:01.410 回答
1

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

private static final Object LOCK = new Object();

然后你使用这个对象编写同步块并像这样调用它的方法:

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

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

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

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

于 2012-06-20T09:34:01.623 回答
1

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

于 2012-06-20T09:40:35.710 回答