我在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 类中的布尔字段,其中包含 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的唯一功能。它是一个顶级水平&#34;功能在 SerialPortDevice 类中,用于处理与设备之间的通信发送和接收。
所以沟通看起来像这样:
按钮点击 - &gt;按钮处理程序调用 - &gt; device.singleCommunication(buttons_specific_communicate) - &gt;一些方法运行,然后它来到waitForMessage - &gt; 方法等待事件的1秒 - &gt;事件发生(每次 - 我得到&#34;串行事件发生&#34;沟通) - &gt; readEventOccured设置为true - &gt;如果剩下一些时间(总是一些时间,一切都持续几毫秒),则在waitForMessage方法中收到消息。
如果我点击一个短的(在人的意义上,例如2-3s)延迟按钮,或者我没有点击这些按钮,它们会在他们的处理程序内发送多个通信,这是没有问题的。在不同情况下会发生奇怪的事情。我仍然收到消息&#34; Serial Event Occured&#34; (所以我认为 readEventOccured 也被设置为true)但是waitForMessage函数没有执行
if(readEventOccured)
声明的代码。此外,我必须再次运行应用程序才能与设备通信(我的意思是接收数据,发送工作完美)。
解决了我的问题是添加&#34; volatile&#34; readEventOccured标志的修饰符(顺便说一句,有时候事情变得很快)。但它并不让我高兴。我想让代码在没有&#34; volatile&#34;的情况下正确运行。我的同事提出了一个想法,当我点击按钮并调用通信时,正在创建的线程出现问题 - 也许某些东西会阻塞其他东西?我做的第一件事是打印所有当前runnig线程和...男人,它解决了一个问题。应用程序不再挂了。真的,我表演了#34;挂&#34;情景10-20次,有和没有
Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
System.out.println(threadSet);
在waitForMessage方法的开头,结果是毫不含糊的 - 它以某种方式解决了我的问题。
我几乎可以肯定,获取和打印线程本身并不是解决方案。这是通过这种方式完成的事情,但我无法弄清楚这是什么。有用的信息吗?也许更好地理解Java中的Threads对我有帮助吗?或者它是别的什么?
干杯
答案 0 :(得分:3)
您正在做的是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。不幸的是,他们没有创造出正确的序列,但是再多推几点,你就可以意外地做到正确。这里的关键词是&#34;意外地&#34;,你根本不应该依赖这种行为。
所以将readEventOccurred
标记为volatile
类可以解决问题,但是进一步了解我们可以看到你的waitForMessage()
旋转等待,这很少是一个好主意。我会看一下CountDownLatch
类,例如,为类似场景设计的。 (更好的候选人是其密友,CyclicBarrier
。)
答案 1 :(得分:2)
解决了我的问题是添加&#34; volatile&#34; readEventOccured标志的修饰符(顺便说一句,有时候事情变得很快)。但它并不让我高兴。我想让代码在没有&#34; volatile&#34;。
的情况下正确运行
添加volatile修复问题的事实表明,当涉及多个线程时,您会遇到Java内存模型缺乏保证。简单地说,除了特定情况之外,无法保证在一个线程上的更改何时在其他线程上可见。
印刷和修复的可能原因&#39;问题在于它:
多线程通信很难,所以我建议您查看java.util.concurrent中可用的类,它们可以保证您可以更优雅地解决问题。