为什么在Java中打印所有正在运行的线程会取消阻止应用程序?

时间:2016-08-19 09:11:43

标签: java multithreading javafx

我在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对我有帮助吗?或者它是别的什么?

干杯

2 个答案:

答案 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;问题在于它:

  1. 更改程序的时间,可能意味着它失败的情况不再存在。
  2. 暂时挂起线程以读出其当前状态,也许JVM也将其用作同步内存的机会。
  3. 多线程通信很难,所以我建议您查看java.util.concurrent中可用的类,它们可以保证您可以更优雅地解决问题。