codahale指标如何Meter mark()方法线程安全?

时间:2017-06-08 12:47:35

标签: java multithreading dropwizard metrics codahale-metrics

我最近开始学习CodaHale / DropWizard指标库。我无法理解Meter类是如何线程安全的(根据文档),特别是mark()和tickIfNecessary()方法:

https://github.com/dropwizard/metrics/blob/3.2-development/metrics-core/src/main/java/com/codahale/metrics/Meter.java#L54-L77

public void mark(long n) {
    tickIfNecessary();
    count.add(n);
    m1Rate.update(n);
    m5Rate.update(n);
    m15Rate.update(n);
}

private void tickIfNecessary() {
    final long oldTick = lastTick.get();
    final long newTick = clock.getTick();
    final long age = newTick - oldTick;
    if (age > TICK_INTERVAL) {
        final long newIntervalStartTick = newTick - age % TICK_INTERVAL;
        if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) {
            final long requiredTicks = age / TICK_INTERVAL;
            for (long i = 0; i < requiredTicks; i++) {
                m1Rate.tick();
                m5Rate.tick();
                m15Rate.tick();
            }
        }
    }
}

我可以看到有一个类型为AtomicLong的lastTick,但仍然可能存在m1-m15速率更长一点的情况,因此另一个线程可以调用这些节拍以及下一个TICK_INTERVAL的一部分。由于费率的tick()方法根本没有同步,因此不会成为竞争条件吗? https://github.com/dropwizard/metrics/blob/3.2-development/metrics-core/src/main/java/com/codahale/metrics/EWMA.java#L86-L95

public void tick() {
    final long count = uncounted.sumThenReset();
    final double instantRate = count / interval;
    if (initialized) {
        rate += (alpha * (instantRate - rate));
    } else {
        rate = instantRate;
        initialized = true;
    }
}

谢谢,

玛丽安

2 个答案:

答案 0 :(得分:1)

据我所知,你是对的。如果tickIfNecessary()在另一个调用仍在运行时调用age > TICK_INTERVAL,则可能会从多个线程同时调用m1Rate.tick()和其他tick()方法。因此,归结为tick(),其调用的例程/操作是安全的。

让我们剖析tick()

public void tick() {
    final long count = uncounted.sumThenReset();
    final double instantRate = count / interval;
    if (initialized) {
        rate += (alpha * (instantRate - rate));
    } else {
        rate = instantRate;
        initialized = true;
    }
}

alphainterval仅在实例初始化时设置,并标记为 final 那些线程安全的,因为只读。 countinstantRate是本地的,而其他线程无法看到。 rateinitialized被标记为 volatile ,并且这些写入应始终对以下读取可见。

如果我没有错,几乎从第一次阅读initializedinitializedrate的最后一次写入,这对于比赛是开放的,但是有些效果没有当2个线程争夺initializedtrue的转换时。

似乎大多数有效种族都可以在rate += (alpha * (instantRate - rate));中发生,特别是丢弃或混合计算,如:

  1. 假设:initializedtrue
  2. 主题1:计算countinstantRate,检查initialized,第一次读取rate,我们称之为previous_rate,无论出于何种原因停止
  3. 线程2:计算countinstantRate,检查initialized,并计算rate += (alpha * (instantRate - rate));
  4. Thread1:继续其操作并计算rate += (alpha * (instantRate - previous_rate));
  5. 如果读取和写入以某种方式被排序,以便在所有线程上读取rate然后写入所有线程,从而有效地丢弃一个或多个计算,则会发生丢弃。

    但是这种种族的概率,意味着两个age > TICK_INTERVAL匹配使得2个线程遇到相同的tick()方法,特别是rate += (alpha * (instantRate - rate))可能会非常低并且取决于值不明显。

    mark()方法似乎是线程安全的,只要LongAdderProxy使用update / add的线程安全数据结构和{{{} 1 {} tick()中的方法。

    我认为只有那些能够回答问题的人才会开放 - 这些比赛没有明显影响或以其他方式减轻 - 是项目作者或对项目的这些部分和计算值有深入了解的人。 / p>

答案 1 :(得分:1)

它是线程安全的,因为来自tickIfNecessary()的这一行每{?1}}只返回一次

newIntervalStartTick

如果两个线程几乎同时进入if (lastTick.compareAndSet(oldTick, newIntervalStartTick)) 会怎样?

两个线程都从tickIfNecessary()读取相同的值,确定至少oldTick纳秒已经过去并计算TICK_INTERVAL

现在两个线程都尝试newIntervalStartTick。正如名称lastTick.compareAndSet(oldTick, newIntervalStartTick)所暗示的,此方法与compareAndSetlastTick的当前值进行比较,并且仅当值等于oldTick时,它才会被oldTick原子替换并返回true。

由于这是一个原子指令(在硬件级别!),只有一个线程可以成功。当另一个线程执行此方法时,它会将newIntervalStartTick视为newIntervalStartTick的当前值。由于此值不再与lastTick匹配,因此更新失败,并且该方法返回false,因此此线程不会将oldTick调用m1Rate.tick()

m15Rate.tick()方法使用EWMA.update(n)来累积事件计数,从而提供类似的线程安全保证。