【问题标题】:Is this code multi-thread safe?这段代码多线程安全吗?
【发布时间】:2013-02-20 11:24:20
【问题描述】:
    private static Map<Integer, String> map = null;

    public static String getString(int parameter){

        if(map == null){

            map = new HashMap<Integer, String>();
            //map gets filled here...

        }

        return map.get(parameter);
    }

随着多线程的发展,该代码是否不安全?

【问题讨论】:

  • 这是什么语言?爪哇?

标签: java multithreading static-methods


【解决方案1】:

在多线程场景中,您确实冒着初始化 map 两次的风险。

在托管语言中,垃圾收集器最终会处理不再引用的实例。在非托管语言中,您永远不会释放为覆盖映射分配的内存。

无论哪种方式,初始化都应该得到适当的保护,这样多个线程就不会同时运行初始化代码。

一个原因:第一个线程可能正在初始化 HashMap,而第二个线程很长,看到 map 不为空,并愉快地尝试使用部分初始化的数据结构。

【讨论】:

  • 大大改变了问题。
【解决方案2】:

比赛条件?可能。

如果mapnull,并且两个线程同时检查if (map == null),每个线程都会分配一个单独的映射。这可能是也可能不是问题,主要取决于map 是否不变。即使地图是不变的,填充地图的成本也可能成为一个问题。

内存泄漏?没有。

无论竞争条件如何,垃圾收集器都会正确完成其工作。

【讨论】:

  • 嗯...内存泄漏:也许。虽然这看起来像 Java 之类的托管语言 (=no),但 OP 实际上并未指定。
  • 实际上,更有可能出现竞争条件失败的情况是我在回答中描述的情况(假设填充地图的时间成本大于分配空地图的成本)。
【解决方案3】:

由于竞态条件,在多线程情况下是不安全的。

但是你真的需要地图的延迟初始化吗?如果无论如何都要使用地图,似乎你可以为它做急切的初始化..

【讨论】:

    【解决方案4】:

    上面的代码不是线程安全的,正如其他人所提到的,你的地图可以被初始化两次。您可能很想尝试通过添加一些同步来修复上述代码,这被称为“双重检查锁定”,Here is an article 描述了这种方法的问题,以及一些潜在的修复。

    最简单的解决方案是将字段设为单独类中的静态字段:

    class HelperSingleton {
      static Helper singleton = new Helper();
     }
    

    也可以使用 volatile 关键字来修复,如 Bill Pugh 的文章中所述。

    【讨论】:

      【解决方案5】:

      如前所述,这绝对不安全。如果地图的内容不是基于 getString() 中的参数,那么最好将地图初始化为静态初始化程序,如下所示:

      private static final Map<Integer, String> MAP = new HashMap<Integer,String>();
      
      static {
        // Populate map here
      }
      

      上面的代码在类加载时被调用一次。它是完全线程安全的(尽管未来对地图的修改不是)。 您是否出于性能原因尝试延迟加载它?如果是这样,那就更安全了:

      private static Map<Integer, String> map = null;
      
      public synchronized static String getString(int parameter){
      
          if(map == null){
      
              map = new HashMap<Integer, String>();
              //map gets filled here...
      
          }
      
          return map.get(parameter);
      }
      

      使用synchronized 关键字将确保在任何时候只有一个线程可以执行该方法,并且始终传播对地图引用的更改。

      如果您要问这个问题,我建议您阅读“Java 并发实践”。

      【讨论】:

      • +1 为 JCIP,有点难以通过,但这是我很高兴阅读的那种书。
      • 同意保罗,几年后我仍然不理解整个事情。不过很值得……
      【解决方案6】:

      不,此代码对于多线程使用是不安全的。

      地图初始化时存在竞争条件。例如,多个线程可以同时初始化映射并破坏彼此的写入。

      没有内存屏障来确保线程所做的修改对其他线程可见。例如,每个线程都可以使用自己的映射副本,因为它们永远不会“看到”另一个线程写入的值。

      没有原子性可以确保在同时访问映射时保留不变量。例如,执行get() 操作的线程可能会进入无限循环,因为另一个线程在同时进行put() 操作期间重新散列存储桶。

      【讨论】:

        【解决方案7】:

        如果您使用的是 Java 6,请使用 ConcurrentHashMap

        ConcurrentHashMap JavaDoc

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-18
          • 1970-01-01
          • 1970-01-01
          • 2011-04-16
          • 1970-01-01
          相关资源
          最近更新 更多