【问题标题】:Java non-final static Map Operation in Static Block静态块中的 Java 非最终静态映射操作
【发布时间】:2020-02-01 12:59:42
【问题描述】:

我试图了解以下代码是否是线程安全的。我在 SO 中经历了很多很多问题,但似乎找不到明确的答案。

class Car {
   public static Map<String, String> features = new HashMap<>();
   static {
       features.put("color", "red");
       features.put("foo", "bar");
   }
   public Comparable<?> getValue(String id) {
       if(!features.containsKey(id)) {
          features.put(id, id);
       }
       String res = features.get(id);
       // some business logic and return stmt.
   }
}

我们最近在我们的应用程序中遇到了意外行为,其中getValue("color") 返回的值为空。我一直无法重现这个问题,但它似乎是在同时处理两个线程时发生的。

  1. features 映射仅在 getValue 方法中修改,前提是该参数在映射中尚不可用。
  2. 静态块中用于初始化地图的参数出现问题。
  3. 用例示例 - Car c = new Car(); c.getValue("color");

任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 请贴出方法的逻辑。我相信问题就在那里,因为静态块是在应用程序开始时初始化的。
  • @Steyrix - 更新了方法定义。
  • @Eng.Fouad - 谢谢!我将进一步探索这个选项。

标签: java multithreading static thread-safety


【解决方案1】:

不,这不是线程安全的。

来自Javadoc of HashMap

请注意,此实现是不同步的。如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改映射,则它必须同步外部。

所以,同步访问地图:

String res;
synchronized (features) {
  if(!features.containsKey(id)) {
    features.put(id, id);
  }
  res = features.get(id);
}

并创建字段final,并在静态初始化器内部同步。

或者,更好的是,使用ConcurrentHashMapcomputeIfAbsent 方法。

String res = concurrentFeatures.computeIfAbsent(id, k -> id);

HashMap 在 Java 8+ 中也有一个 computeIfAbsent 方法,但您也需要在同步块中调用它)。


实际上,在 Java 8+ 中执行此操作的更好方法是使用 getOrDefault,假设您实际上不需要保留以前看不见的键/值对:

res = features.getOrDefault(id, id);

这不会改变映射,因此您不必担心这里的线程安全;你只需要确保它被安全地初始化:

public final static Map<String, String> features;
static {
   Map<String, String> f = new HashMap<>();
   f.put("color", "red");
   f.put("foo", "bar");
   features = f;
}

【讨论】:

  • 谢谢 - @Eng.Fouad 提出了非常相似的建议。虽然我无法重现这一点(对此并不感到惊讶) - 我同意这确实是问题所在。有趣的是,这在过去 8 年里一直运行良好.. :-)
  • @user2780757 关于线程安全问题的有害之处在于缺乏正确功能的保证与不正确功能的保证不同。如果它已经“正常”了 8 年,那么你要么很幸运,要么只是没有发现问题。
猜你喜欢
  • 2019-11-13
  • 1970-01-01
  • 1970-01-01
  • 2011-06-02
  • 1970-01-01
  • 1970-01-01
  • 2017-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多