我在JavaFX中创建了一个GUI应用程序,该应用程序与串行设备通信(我使用jssc connector)。当我想要获取数据时,我发送一个通信,然后等待1秒钟,直到等待功能终止。当我单击其中一个按钮(用于启动设备,进行标识等)时,也会发送通信。在开发过程中,我注意到一个错误-当我单击太多按钮或单击按钮发送了多个通信时,通信挂起(但仅在接收消息时,我仍然可以发送单向通信以启动设备)。 。
通信主要由我自己的类 SerialPortDevice 处理。我创建一个类类型的对象,然后调用特定的方法。这是等待消息的方法:
private String waitForMessage() throws SerialPortException {
long operationStartTime = System.currentTimeMillis();
long connectionTimeout = SerialPortCommunicationParameters.CONNECTION_TIMEOUT_IN_MILLIS;
String resultMessage = "";
do {
if (readEventOccurred) {
System.out.println();
resultMessage = receiveMessage();
System.out.println("After receiving a message");
messageReceived = true;
}
} while (((System.currentTimeMillis() - operationStartTime) < connectionTimeout) && (!messageReceived));
if (!readEventOccurred) {
resultMessage = NO_RESPONSE;
}
System.out.println("Time elapsed: " + (System.currentTimeMillis() - operationStartTime + "ms"));
return resultMessage;
}
可以注意到只有当标志 readEventOccured 为真时,才接收消息。它由我的SerialPortEventListener实现处理:
class SerialPortDeviceReader implements SerialPortEventListener {
private SerialPortDevice device;
SerialPortDeviceReader(SerialPortDevice device) {
this.device = device;
}
public void serialEvent(SerialPortEvent event) {
if (event.isRXCHAR()) {
System.out.println("Serial Event Occured!");
device.setReadEventOccurred(true);
}
}
}
readEventOccured 是 SerialPortDevice 类中的一个 boolean 字段,其中包含 waitForMessage 函数。另外, waitForMessage 由另一个函数 singleConversation 调用:
String singleConversation(String testMessage) {
String resultMessage = NO_RESPONSE;
try {
openConnection();
sendMessage(testMessage);
resultMessage = waitForMessage();
closeConnection();
} catch (SerialPortException e) {
e.printStackTrace();
return resultMessage;
}
System.out.println();
readEventOccurred = false;
messageReceived = false;
return resultMessage;
}
...这是将 readEventOccured 设置为false的唯一函数。它是 SerialPortDevice 类中的“顶级”功能,用于处理与设备之间的通信收发。
因此通信如下所示:
单击按钮->按钮处理程序调用-> device.singleCommunication(buttons_specific_communicate)->运行某些方法,然后进入waitForMessage->方法为事件等待1s->事件发生(每次-我收到“发生的串行事件”通信)->将readEventOccured设置为true->如果还剩一些时间(总是还有一些时间,那么一切都将持续数毫秒),则在waitForMessage方法中接收到消息。
如果我单击按钮的延迟很短(从人类的角度来看,例如2-3秒),或者我不单击这些按钮确实会在其处理程序中发送多个通信,则没有问题。在不同的情况下,会发生奇怪的事情。我仍然收到消息“发生了串行事件”(因此我想 readEventOccured 也被设置为true),但是waitForMessage函数未执行
if(readEventOccured)
声明的代码。另外,我必须再次运行应用程序才能与设备进行通信(我的意思是接收数据,发送工作正常)。
解决我的问题的方法是在readEventOccured标志中添加“volatile”修饰符(顺便说一句,事情有时进展很快)。但这并不能取悦我。我想使代码正常运行而不会出现“volatile”。我的同事提出一个想法,当我单击按钮并调用通信时,正在创建的线程出了点问题–也许某处在某处阻止了其他内容?我做的第一件事是打印所有当前的runnig线程,然后...伙计,它解决了一个问题。应用程序不再挂起。真的,我在有或没有的情况下执行了10到20次“挂起”场景
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
System.out.println(threadSet);
在waitForMessage方法的开头,结果是明确的-它以某种方式消除了我的问题。
我几乎可以肯定,获取和打印线程本身并不是解决方案。顺便说一句,这是正在做的事情,但我不知道这是什么。有什么有用的信息吗?也许更好地了解Java中的线程会对我有帮助?还是其他?
干杯
最佳答案
您正在做的是textbook example of what happens when there are no visibility guarantees。如果我们将您的代码分解为必不可少的部分,则会得到如下所示的内容:
boolean flag = false;
void consume() {
while (true) {
if (flag) {
//we received flag, hooray
}
}
}
void produce() {
flag = true;
}
现在,如果
produce()
和consume()
在不同的线程中运行,则绝对不能保证consume()
永远不会将flag
设置为true。 volatile
创建一个内存屏障,这意味着这些变量的所有读/写都将被完全排序。但是,代码中还有很多
System.out.println()
行。由于它们本身被同步,它们使图片复杂化,因此在代码的不同部分之间创建了happens-before relationships。不幸的是,他们没有创建正确的序列,但是又戳了几下,您可能会偶然将其正确设置。这里的关键词是“偶然地”,您完全不应该依赖这种行为。因此,将
readEventOccurred
标记为volatile
可以解决此问题,但再进一步一点,我们可以看到waitForMessage()
旋转等待,这很少是个好主意。我将看看例如针对类似情况设计的 CountDownLatch
类。 (一个更好的人选是它的密友 CyclicBarrier
。)