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

Posted

技术标签:

【中文标题】计时器类中的潜在竞争条件?【英文标题】:Potential race condition in the timer class? 【发布时间】:2015-06-17 23:41:40 【问题描述】:

我编写了一个计时器,它可以测量任何多线程应用程序中特定代码的性能。在下面的计时器中,它还将用 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;
    

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

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 可能已过时?

【问题讨论】:

这个话题是给codereview.stackexchange.com的 @AlexeiKaigorodov 仅当代码按预期工作时,OP 不确定。在 Code Review 中损坏的代码是 off-topic 【参考方案1】:

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

另一种方法是将 AtomicLongs 放入地图中。然后你放置/获取 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;
);

【讨论】:

是的,我的意思是说有没有其他方法可以做到这一点,而不是像我现在做的那样循环? computeIfAbsent 返回有效值,因此它有意允许将操作写为单个语句 histogram.computeIfAbsent(result, AtomicLong::new).incrementAndGet(); 而无需另一个 get,只读取一次哈希而不是两次……即使没有 AtomicLong,新的 API 也允许优化调用:histogram.merge(result, 1L, Long::sum);,这已经是一个原子更新。

以上是关于计时器类中的潜在竞争条件?的主要内容,如果未能解决你的问题,请参考以下文章

CreateTimerQueueTimer 回调和竞争条件

停止刷新令牌的竞争条件?

外部类中的 NSTimer

从基类调用派生类中的函数

我们可以称程序中的潜在障碍吗

为啥“删除”这个无锁堆栈类中的节点会导致竞争条件?