【问题标题】:How to populate map of string and another map in a thread safe way?如何以线程安全的方式填充字符串映射和另一个映射?
【发布时间】:2016-12-05 20:59:37
【问题描述】:

我正在使用incrementdecrement 指标的以下类来测量我的应用程序指标。

public class AppMetrics {
  private final AtomicLongMap<String> metricCounter = AtomicLongMap.create();

  private static class Holder {
    private static final AppMetrics INSTANCE = new AppMetrics();
  }

  public static AppMetrics getInstance() {
    return Holder.INSTANCE;
  }

  private AppMetrics() {}     

  public void increment(String name) {
    metricCounter.getAndIncrement(name);
  }

  public AtomicLongMap<String> getMetricCounter() {
    return metricCounter;
  }
}

我正在从多线程代码中调用AppMetrics 类的increment 方法,以通过传递指标名称来增加指标。

问题陈述:

现在我想为每个clientId 拥有metricCounter,这是一个字符串。这意味着我们也可以多次获得相同的clientId,有时它会是一个新的clientId,因此我需要为该clientId 提取metricCounter 映射并在该特定映射上增加指标(即我不知道该怎么做)。

记住它必须是线程安全的并且必须执行原子操作的正确方法是什么。我正在考虑制作这样的地图:

  private final Map<String, AtomicLongMap<String>> clientIdMetricCounterHolder = Maps.newConcurrentMap();

这是正确的方法吗?如果是,那么我如何通过传递 clientId 作为键来填充此映射,它的值将是每个指标的计数器映射。

我使用的是 Java 7。

【问题讨论】:

  • 哎呀,我在使用 Java 7。抱歉忘了提。
  • 使用并发映射是一个好的开始,但确保更新是原子的则比较棘手,尤其是如果您不在 Java 8 上。
  • 切换到 Java 8 :-) 说真的,你应该。尽快。
  • 如果争用不是主要问题,您可能需要考虑传统锁定而不是并发集合。

标签: java multithreading concurrency thread-safety guava


【解决方案1】:

如果您使用地图,那么您需要在创建新的AtomicLongMap 实例时进行同步。我建议改用LoadingCache。您可能最终不会使用任何实际的“缓存”功能,但“加载”功能非常有用,因为它将为您同步创建 AtomicLongMap 实例。例如:

LoadingCache<String, AtomicLongMap<String>> clientIdMetricCounterCache =
        CacheBuilder.newBuilder().build(new CacheLoader<String, AtomicLongMap<String>>() {
            @Override
            public AtomicLongMap<String> load(String key) throws Exception {
                return AtomicLongMap.create();
            }
        });

现在您可以安全地开始更新任何客户端的指标计数,而无需担心客户端是否是新的。例如

clientIdMetricCounterCache.get(clientId).incrementAndGet(metricName);

【讨论】:

  • 我明白了。这非常好..我根据您的建议使用示例代码更新了问题。你说的对吗?
  • 是的,虽然我不会公开私有缓存。您不希望 AppMetrics 实例的客户端对其进行修改等。您可能会将缓存内容作为不可修改的地图视图返回。例如Collections.unmodifiableMap(clientIdMetricCounterCache.asMap())。如果给定客户端没有可用的指标,这也将防止客户端调用创建额外的 AtomicLongMap 实例。它也不会在您的AppMetrics API 中公开LoadingCache,以防您决定放弃它,您只需要继续遵守Map 接口即可。
  • 对不起,我离开了一段时间.. 这是一个很好的观点。让我向您解释我正在尝试做什么,以便您可以给我一些想法或想法,无论我做得对与否。我正在尝试创建一个指标类(AppMetrics),它将我的所有指标保存 30 秒,然后我想重置这些指标。所以每 30 秒我的所有指标都会被重置。
  • 我就是这样做的。我以多线程的方式从代码中的不同位置填充AppMetrics 类中的所有指标。然后我有一个不同的类,它每 30 秒运行一次,从我调用 AppMetrics 类获取所有指标,通过迭代将这些指标发送到其他系统并自动重置它们。
  • 我已经用代码细节更新了这个问题,它会告诉你我想要做什么。如果您对此有任何想法或想法,请告诉我。
【解决方案2】:

Map&lt;String, Map&lt;String, T&gt;&gt; 只是伪装的Map&lt;Pair&lt;String, String&gt;, T&gt;。创建一个MultiKey 类:

class MultiKey {
    public String clientId;
    public String name;

    // be sure to add hashCode and equals
}

然后只需使用AtomicLongMap&lt;MultiKey&gt;

已编辑:

如果指标集定义明确,使用此数据结构查看一个客户的指标不会太难:

Set<String> possibleMetrics = // all the possible values for "name"

Map<String, Long> getMetricsForClient(String client) {
    return Maps.asMap(possibleMetrics, m -> metrics.get(new MultiKey(client, m));
}

返回的地图将是一个实时的不可修改的视图。如果您使用的是较旧的 Java 版本,这可能会有点冗长,但仍然可以。

【讨论】:

  • 这里的clientmetricsType 是什么?我没听懂?
  • 对不起。 clientIdname 在您的示例中。我会编辑帖子。
  • 这是个好主意,但我认为 OP 正在通过迭代键集来收集指标。这样他就必须遍历所有客户的指标并过滤掉特定客户的指标。
  • @shmosel 好点。数据结构的选择取决于哪个更频繁地完成,增加指标还是查看它们。
  • 是的,没错,我想遍历键集以收集所有指标。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 2019-07-08
  • 2014-07-11
  • 2019-09-20
  • 2011-03-14
相关资源
最近更新 更多