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

Xen*_*non 5 java multithreading metrics dropwizard 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();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我可以看到有一个 AtomicLong 类型的lastTick,但仍然可能存在 m1-m15 速率滴答时间稍长的情况,因此另一个线程可以调用这些滴答以及下一个 TICK_INTERVAL 的一部分。由于 Rates 的 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢,

玛丽安

mak*_*dev 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

alphainterval在实例初始化时设置,并标记为final,因为只读,所以线程安全。count并且instantRate是本地的并且无论如何对其他线程都是不可见的。rate并被initialized标记为易失性,并且这些写入对于后续读取应该始终可见。

如果我没记错的话,几乎从第一次读取initialized到最后一次写入,initialized或者rate这对竞争开放,但有些没有效果,比如当 2 个线程竞争切换initializedto时true

似乎大多数有效的竞赛都可以在rate += (alpha * (instantRate - rate));特别下降或混合的计算中发生,例如:

  1. 假设:initializedtrue
  2. Thread1:计算countinstantRate检查、执行我们调用的initialized第一次读取,并且由于某种原因停止rateprevious_rate
  3. 线程2:计算countinstantRate检查initialized、计算rate += (alpha * (instantRate - rate));
  4. Thread1:继续运行并计算rate += (alpha * (instantRate - previous_rate));

如果读取和写入以某种方式排序,以便rate在所有线程上读取,然后在所有线程上写入,从而有效地丢弃一个或多个计算,则会发生丢弃。

但是这种竞争的概率,意味着两个age > TICK_INTERVAL线程都遇到相同的tick()方法,尤其是rate += (alpha * (instantRate - rate))可能非常低,并且取决于不明显的值。

mark()只要对/和中的方法LongAdderProxy使用线程安全的数据结构,该方法似乎就是线程安全的。updateaddtick()sumThenReset

我认为唯一能够回答悬而未决的问题的人——无论竞赛没有明显的影响还是以其他方式减轻——是项目作者或对项目的这些部分和计算值有深入了解的人。