我有一个带有两个Threads的ThreadManager。一个用于gui相关请求,一个用于与测量相关的请求。它们都在运行并检查它们的请求队列,如果有的话,它们正在处理请求。可以使用静态ThreadManager.addGuiRequest(eGuiRequest)
和ThreadManager.addMeasRequest(eMeasRequest)
方法随时添加请求。现在需要初始化这两个,这是通过向相应的队列添加INIT
请求来完成的。但是测量的初始化取决于gui已经初始化的事实。我尝试使用wait()/notify()
来解决这个问题,但我无法让它发挥作用。
这是一个SSCCE。在启动时,两个队列都添加了一个INIT请求,然后启动。测量初始化检测到gui尚未初始化并且执行wait()
。 gui初始化(通过睡眠模拟5秒)。一切正常。
gui初始化后,它会尝试唤醒测量线程,但测量线程没有唤醒......我将wait()/notify()
代码基于this article。这里出了什么问题?
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);
}
}
答案 0 :(得分:3)
GUI线程在notify()
(类型为Thread)上调用measThread
,processMeasurementRequest()
方法在wait()
上调用this
,这是Runnable实例由measThread
使用。
我建议使用一个特定的对象,由两个线程共享以等待并通知:
private static final Object GUI_INITIALIZATION_MONITOR = new Object();
此外,我不使用LinkedList并在请求之间休眠一段时间,而是使用BlockingQueue:这将允许消费线程在有请求时立即处理请求,并避免从休眠状态唤醒不必要的事件
此外,您可以使用初始化为1的CountDownLatch代替低级等待/通知。当初始化时,GUI线程将countDown()
锁存,并且测量线程将await()
锁定,直到GUI线程调用countDown()
。这会将复杂的同步和通知内容委托给更高级别,经过良好测试的对象。
答案 1 :(得分:1)
主要问题是您在notify()
上调用measThread
,但在匿名类上调用了wait()
。解决此问题的最简单方法是创建一个特殊对象进行同步。例如,您创建一个字段:
private static final Object LOCK = new Object();
然后使用此对象编写同步块并调用其方法:
synchronized (LOCK) {
while (!initialized) LOCK.wait();
}
此外,我不得不说这段代码根本不会对从不同线程访问的字段使用任何同步,这意味着它可以随时中断。这两个队列都是在你创建的线程之外访问的,这意味着你应该一直使用一个锁来访问它们,或者你可以使用内置的同步列表来保证它们的线程安全:
quiQueue = Collections.synchronizedList(new LinkedList<eGuiRequest>());
从同步块访问 initialized
,但是现在它们在不同的锁上同步(我在回答的开头描述了这个问题)。如果您解决了这个问题,initialized
也将正常工作。
答案 2 :(得分:1)
请勿在启动时向测量发送init请求。执行init gui请求后从processGuiRequest()发送它。然后不需要等待/通知的东西。