计时器 class 中的潜在竞争条件?

Potential race condition in the timer class?

我编写了一个计时器,它可以测量任何多线程应用程序中特定代码的性能。在下面的计时器中,它还会用 x 毫秒的调用次数填充地图。我将使用这张地图作为我的直方图的一部分来做进一步的分析,比如多少百分比的调用花费了这么多毫秒等等。

public static class StopWatch {

    public static ConcurrentHashMap<Long, Long> histogram = new ConcurrentHashMap<Long, Long>();

    public static StopWatch getInstance() {
        return new StopWatch();
    }

    private long m_end = -1;
    private long m_interval = -1;
    private final long m_start;

    private StopWatch() {
        m_start = m_interval = currentTime();
    }

    public long getDuration() {
        long result = 0;

        final long startTime = m_start;
        final long endTime = isStopWatchRunning() ? currentTime() : m_end;

        result = convertNanoToMilliseconds(endTime - startTime);

        boolean done = false;
        while (!done) {
            Long oldValue = histogram.putIfAbsent(result, 1L);
            if (oldValue != null) {
                done = histogram.replace(result, oldValue, oldValue + 1);
            } else {
                done = true;
            }
        }

        return result;
    }

    public long getInterval() {
        long result = 0;

        final long startTime = m_interval;
        final long endTime;

        if (isStopWatchRunning()) {
            endTime = m_interval = currentTime();
        } else {
            endTime = m_end;
        }

        result = convertNanoToMilliseconds(endTime - startTime);

        return result;
    }

    public void stop() {
        if (isStopWatchRunning()) {
            m_end = currentTime();
        }
    }

    private long currentTime() {
        return System.nanoTime();
    }

    private boolean isStopWatchRunning() {
        return (m_end <= 0);
    }

    private long convertNanoToMilliseconds(final long nanoseconds) {
        return nanoseconds / 1000000L;
    }
}

例如,这是我将使用上面的计时器 class 来测量多线程应用程序中特定代码的性能的方法:

StopWatch timer = StopWatch.getInstance();
//... some code here to measure
timer.getDuration();

现在我的问题是 - 如果您查看 getDuration 方法,我还会使用诸如调用次数 x 毫秒等信息填充我的地图,以便我稍后可以使用该地图进行进一步分析比如计算平均值、中位数、第 95 个和第 99 个百分位数。我的以下代码线程安全还是存在竞争条件?

boolean done = false;
while (!done) {
    Long oldValue = histogram.putIfAbsent(result, 1L);
    if (oldValue != null) {
        done = histogram.replace(result, oldValue, oldValue + 1);
    } else {
        done = true;
    }
}

在调用 Long oldValue = histogram.putIfAbsent(result, 1L);done = histogram.replace(result, oldValue, oldValue + 1); 之间,地图中的值可能发生了变化。因此,oldValue 可能会过时?

你说的部分看起来是正确的。是的,有时 oldValue 会过时,但这就是你循环的原因。对吗?

另一种方法是将 AtomicLongs 放入映射中。然后你 put/get AtomicLong 并递增它。

histogram.putIfAbsent(result, new AtomicLong());
histogram.get(result).incrementAndGet();

在 java 8 中,您可以使用 compute 和朋友来发挥您的优势(测试并看看您最喜欢哪个):

histogram.computeIfAbsent(result, AtomicLong::new);
histogram.get(result).incrementAndGet();

// or
if (histogram.putIfAbsent(result, new AtomicLong(1)) == null)
   histogram.get(result).incrementAndGet();

// or even
histogram.compute(result, ($, current) -> {
   if (current == null) return new AtomicLong(1);
   current.incrementAndGet();
   return current;
});