具有静态方法和初始化方法的类的并发性

时间:2012-03-20 14:59:08

标签: java concurrency static

如何使以下类线程安全?

public class Helper
{
    private static Map<String,String> map = null;

    public static init()
    {
        map = new HashMap<String,String>();
        // some loading stuff
    }

    public static getMap()
    {
       if(map==null) init();
       return new HashMap<String,String>(map);
    }
}

到目前为止我的想法:

  1. 使getMap()同步。 问题:使程序比必要的慢,因为只需在程序开始时再进行同步,然后再也不需要。

  2. 使用锁。问题是我在这里使用的方法“isLocked”不存在。那么什么是最好的解决方案?

    public class Helper
    {
     private static Map<String,String> map = null;
     private static Lock lock = new ReentrantLock();
    
     public static init()
      {
         lock.lock();
         map = new HashMap<String,String>();
         // some loading stuff
         lock.unlock();
    }
    
    public static getMap()
    {
       synchronized {if(map==null) init();}
       while(lock.isLocked()) {Thread.wait(1);]
       return new HashMap<String,String>(map);
    }
    

    }

  3. P.S。:很抱歉第二个源代码显示问题。在枚举后使用代码似乎存在错误。

    P.P.S。:我知道HashMap不是线程安全的。但这只意味着我不能并行阅读应该不是问题,是吗?

    P.P.P.S。:我的最终版本(只是内部类),跟随John Vint:

    protected static class LazyLoaded
    {
        static final Map<String,String> map;
        static
        {
            Map<String,String> mapInit = new HashMap<>();
                        // ...loading...
            map = Collections.unmodifiableMap(mapInit); 
        }
    }
    

5 个答案:

答案 0 :(得分:2)

简单地同步对地图参考的访问并不能解决问题。创建映射后,您需要同步访问并可能修改映射内容的操作。例外情况是,如果您知道在初始化期间地图已填充一次,之后仅执行读取操作。在这种情况下,如果没有明确的同步,你可能会很好,因为你的控制流会处理这个问题(知道程序的逻辑只允许在初始化地图后进行读取)。

否则,我建议您使用其中一个ConcurrentMap实现,例如ConcurrentHashMap,因为如果没有锁争用它们相对便宜,并且如果执行读操作仍然提供必要的线程安全性并在地图的生命周期内写作。

至于初始化,因为它是一个静态字段(仅限一个实例),并且创建一个空映射的成本不高,我建议你简单地声明你的地图:

private static final Map<String,String> map = new ConcurrentHashMap<String,String>();

这样您就不需要其他方法中的条件代码,没有参考可见性问题,代码变得更简单。

答案 1 :(得分:2)

我会委托给一个儿童班

public class Helper
{
    public static Map<String,String> getMap()
    {
         return HelperDelegate.map;
    }

    private static class HelperDelegate { 
           private static Map<String,String> map = new HashMap<String,String>();
           static{
                 //load some stuff
           } 

    }
}

因为类加载是线程安全的,并且只发生一次,这将允许您懒惰地初始化Map。

答案 2 :(得分:0)

在init方法中进行双重空检查:

public init() {
    if (map==null) {
        synchronized(Helper.class) {
            if (map==null)
                 map = new HashMap();
        }
    }
}

顺便说一下:

  1. HashMap不是线程安全的。
  2. 对实例方法进行同步是不是线程安全的,因为你访问一个静态变量,即它是一个类变量,而不是一个实例变量

答案 3 :(得分:0)

首先,除非用新的java.util.ConcurrentHashMap()替换新的HashMap(),否则还需要同步对地图的后续访问。 然后,如果在初始化之后,变量map永远不会再设置为null(或任何其他值),那么它可以声明为volatile,方法getMap()保持不同步,ant方法init()进行同步。方法init()应该再次检查map是否为null。

原始代码中的BTW,生成了2个HashMaps。 getMap()应该返回变量map而不是新的HashMap()。

答案 4 :(得分:-1)

同步init并在那里再次检查null

public class Helper
{
    private static Map<String,String> map = null;

    public synchronized init()
    {
        if(map != null)
            return;
        map = new HashMap<String,String>();
        // some loading stuff
    }

    public getMap()
    {
       if(map==null) init();
       return new HashMap<String,String>(map);
    }
}