我在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 。)

10-02 00:19