4

我最近开始学习 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 的一部分。由于 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;
    }
}

谢谢,

玛丽安

4

2 回答 2

2

它是线程安全的,因为此行 fromtickIfNecessary()仅返回一次 truenewIntervalStartTick

if (lastTick.compareAndSet(oldTick, newIntervalStartTick))

如果两个线程tickIfNecessary()几乎同时进入会发生什么?

两个线程从 读取相同的值oldTick,确定至少TICK_INTERVAL经过纳秒并计算 a newIntervalStartTick

现在两个线程都尝试做lastTick.compareAndSet(oldTick, newIntervalStartTick). 顾名思义compareAndSet,此方法与 to 的当前值进行比较,lastTick并且oldTick仅当该值等于时,oldTick它才会自动替换为newIntervalStartTick并返回 true。

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

EWMA.update(n)方法使用 ajava.util.concurrent.atomic.LongAdder来累积提供类似线程安全保证的事件计数。

于 2017-06-12T05:45:17.810 回答
1

据我所知,你是对的。IftickIfNecessary()被调用使得age > TICK_INTERVAL当另一个调用仍在运行时,可能会从多个线程同时调用m1Rate.tick()其他方法。tick()所以它归结为wether 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;
    }
}

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

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

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

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

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

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

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

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

于 2017-06-08T14:28:24.300 回答