首页 > 解决方案 > Java:在时间关键型应用程序中实现高性能多线程的最佳方法是什么?

问题描述

我正在使用 Java 8 开发网络代理应用程序。对于入口,主要逻辑是数据处理循环:在入站队列中获取数据包,处理内容数据(例如协议采用),并将其放入发送队列。设计中允许多个虚拟 TCP 通道,因此数据处理线程在数据处理线程列表中,在特定的持续时间内处理一堆通道,作为整个工作的一部分(例如,对于具有channel.channelId%NUM_DATA_PROCESSING_THREADS = 0,由负载平衡调度程序确定)。通道存储在一个数组中,并通过使用作为单元格的索引来访问,该单元格被一个提供诸如、、、等channeled方法的类包装,并且该实例被称为registerderegistergetByIdsizeCHANNEL_STORE在节目中。我需要通过不同的线程(至少调度线程、数据处理线程和用于从 GUI 销毁通道的控制操作线程)在主逻辑(数据处理循环)中使用这些方法。然后我需要考虑这些线程之间的并发性。我有几种候选方法:

  1. 在, ,等synchronized周围使用或可重入锁。这是最简单的并且是线程安全的。但是我对锁(CAS)机制有性能问题,因为我需要以非常高的频率在(尤其是)上执行操作。registerderegistergetByIdCHANNEL_STOREgetById

  2. 通过和/或指定对CHANNEL_STORESingleThreadExecutor的操作。值得关注的是在数据处理循环中的每个这样的目的地创建可运行/可调用对象的性能:创建可运行实例并调用——我不知道这会比同步或可重入锁更广泛。在现实中(到目前为止)存在后期操作,因此只需在数据处理循环中放置可运行且无需等待可调用返回,尽管在控制循环中需要后期操作。 executor.execute(runnable)executor.submit(callable)execute在此处输入图像描述

  3. 通过一对ArrayBlockingQueue而不是Executor来指定CHANNEL_STORE对一个专用任务的操作对于每次访问CHANNEL_STORE,将一个task-indicator和一个参数附件一起放到第一个队列中,然后专用线程通过阻塞方法在这个队列上循环take并在CHANNEL_STORE. 然后,它把结果放到第二个队列中,让指定器继续进行后期操作(但是目前不需要)。我认为这是最快的,假设 JVM 中的阻塞队列是无锁的。对此的担忧是代码非常混乱且容易出错。

我认为第 2 和第 3 可能被称为“序列化”。

我不能简单地将任务分配给线程池进行数据处理而忘记它们的原因是每个通道的 TCP 流数据包不能无序,它必须在每个通道基础上串行。

问题:

  1. 与第一种方式相比,第二种方式的性能如何?

  2. 对我的情况有什么建议?

我目前正在使用流 IO 进行 LAN 读/写。如果使用 NIO,NIO 线程和数据处理线程之间的协调可能会带来额外的复杂性(例如 post 操作)。所以我认为这个问题对于像我这样的时间关键型(基于流的、多通道网络)应用程序是有意义的。

标签: javamultithreadingconcurrencytime-critical

解决方案


如果我很好地理解您的用例,这是并发编程中的常见问题。一种解决方案是使用环形缓冲区方法,它通常可以很好地解决同步和太多对象创建问题。

你可以在 lmax dispruptor 库中找到一个很好的实现。请参阅https://lmax-exchange.github.io/disruptor/了解更多信息。但请记住,这不是魔术,必须适应您的用例。


推荐阅读