【问题标题】:Optimising Java code for fast response优化 Java 代码以实现快速响应
【发布时间】:2018-08-16 15:37:24
【问题描述】:

我有一个多线程 Java 应用程序,它使用多个 CPU 密集型线程来收集信息。每隔几分钟,就会发现一个需要由程序的另一个线程处理的结果。找到的结果被添加到列表中,并通知其他相关线程(使用锁和条件),然后处理找到的信息。我需要将这些信息从线程传递到线程的时间延迟尽可能小。在使用 System.currentTimeMillis() 测量唤醒和通知之间的时间时,延迟通常小于 5 毫秒,并且通常小于或等于 1 毫秒。有时,延迟更大(10-20ms)。由于毫秒是计算机的宏单位,我认为可靠地小于 1 毫秒的延迟应该是可能的,这将有利于我的应用程序。

您是否知道造成较大延迟的原因是什么,或者我如何才能找到查看的位置?会不会是垃圾收集?或者线程唤醒的几毫秒变化被认为是正常的?

我在 Linux Ubuntu 虚拟专用服务器上使用 Java 版本 1.8.0。

附上一个程序设计的例子。运行它并不能正确模拟我的生产程序观察到的延迟。 “实际”程序使用大量内存、CPU,并且每隔几分钟只传输一次信息。我尝试过,但未能简单地模拟这个。

谢谢。

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.Condition;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;

public class Example {
    public static void main(String[] args) {
        startInfoThreads();
        startWatcherThread();
    }

    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();
    private static List<Long> infoList = new ArrayList<>();

    private static void startWatcherThread () {
        Thread t = new Thread () {
            @Override
            public void run () {
                while (true) {
                    // Waiting for results...
                    try {
                        lock.lock();
                        while (infoList.size() == 0) {
                            try {
                                condition.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        long delta = System.currentTimeMillis() - infoList.remove(0);
                        if (delta > 0)
                            System.out.println("Time for waking up: " + delta);
                    } finally {
                        lock.unlock();
                    }
                    // Do something with info
                }
            }
        };
        t.start();
    }

    private static void startInfoThreads () {
        for (int i = 0; i < 14; i++) {
            Thread t = new Thread() {
                @Override
                public void run() {
                    Random r = new Random();
                    while (true) {
                        // Gather info, 'hits' about once every few minutes!
                        boolean infoRandomlyFound = r.nextInt(100) >= 99;
                        if (infoRandomlyFound) {
                            try {
                                lock.lock();
                                infoList.add(System.currentTimeMillis());
                                condition.signal();
                            } finally {
                                lock.unlock();
                            }
                        }
                    }
                }
            };
            t.start();
        }
    }
}

【问题讨论】:

  • 查看这篇标题为The slow currentTimeMillis() 的非常详细的文章。我想作者是为你写的!作为一个单独的问题,您可以更新您的问题以指定您的 Java 版本和操作系统。
  • 您的设置有多少个内核以及创建/执行了多少个线程?
  • 8 核心设置。大约 11 个线程正在“猛烈”地收集信息,而另一个线程正在闲置等待信息到达。升级到 16 核设置可能是合适的,但对我目前的主机来说相当昂贵。
  • 代码太多,无法做出突出问题的连贯演示,所以我认为显示一些代码不会有帮助。
  • @Jeroen Re "代码太多了" => 是否可以创建 MCVE?见How to create a Minimal, Complete, and Verifiable example

标签: java multithreading


【解决方案1】:

System.currentTimeMillis() 可能会受到时钟漂移的影响,并且通常具有约 10 毫秒的粒度。

要测量经过的时间,您应该始终使用System.nanoTime(),因为它可以保证准确性。

【讨论】:

  • 能否详细说明时钟漂移的概念?另外,我在其他地方读到 System.nanoTime() 不适合在线程之间进行测量。
  • 感谢您的链接。不过,请在您链接到的线程中查看此答案:stackoverflow.com/a/9314616/6637018
  • @KarolDowbecki "[...] 不保证分辨率至少与文档中的 currentTimeMillis()" 一样好。我们可能会以不同的方式使用“保证”这个词。
  • 如果您在虚拟专用服务器上运行,您的应用程序的行为可能会受到托管在与您的虚拟机相同的物理硬件上的其他虚拟机中发生的任何其他情况的影响 - 因此在毫秒的顺序可能不是不合理的......
  • @Jeroen 注意这个问题:“Linux 计时的大幅跳跃原来是由于不同 CPU 上持有的 TSC 计数器之间的差异。JVM 将定期挂起正在运行的 Java 线程并将其迁移到在不同的内核上运行。这可能会导致 CPU 计数器之间的差异对应用程序代码可见。这意味着 nanoTime() 在很长一段时间内可能变得基本不可信。对于测量较短的持续时间很有用,但在较长(宏观)时间范围内,它应该根据 currentTimeMillis() 重新设定基线。"
【解决方案2】:

它可能不会加快您的进程,但使用BlockingQueue 肯定会使代码更清晰。

当没有信息时,还要注意Thread.sleep

final BlockingQueue<Long> queue = new ArrayBlockingQueue<>(10);

private void startWatcherThread() {
    Thread t = new Thread() {
        @Override
        public void run() {
            while (true) {
                // Waiting for results...
                try {
                    Long polled = queue.poll(1, TimeUnit.SECONDS);
                    // Do something with info
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
    t.start();
}

private void startInfoThreads() {
    for (int i = 0; i < 14; i++) {
        Thread t = new Thread() {
            @Override
            public void run() {
                Random r = new Random();
                while (true) {
                    // Gather info, 'hits' about once every few minutes!
                    boolean infoRandomlyFound = r.nextInt(100) >= 99;
                    if (infoRandomlyFound) {
                        queue.put(System.currentTimeMillis());
                    } else {
                        try {
                            Thread.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
        t.start();
    }
}

private void test() {
    startInfoThreads();
    startWatcherThread();

}

【讨论】:

    猜你喜欢
    • 2012-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多