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);
}
多线程的代码是不安全的吗?
答案 0 :(得分:4)
如上所述,这绝对不安全。如果地图的内容不是基于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 Concurrency in Practice”。
答案 1 :(得分:2)
比赛条件?可能。强>
如果map
为null
,并且两个线程同时检查if (map == null)
,则每个线程将分配一个单独的映射。这可能是也可能不是问题,主要取决于map
是否不变。即使地图不变,填充地图的成本也可能成为一个问题。
内存泄漏?否强>
无论竞争条件如何,垃圾收集器都能正常工作。
答案 2 :(得分:1)
在多线程方案中,您确实冒了两次初始化map
的风险。
在托管语言中,垃圾收集器最终将处理不再引用的实例。在非托管语言中,您永远不会释放为覆盖的地图分配的内存。
无论哪种方式,都应该正确保护初始化,以便多个线程不会同时运行初始化代码。
一个原因:第一个线程可能正在初始化HashMap,而第二个线程很长,看到map
不为空,并且愉快地尝试使用部分初始化的数据结构。
答案 3 :(得分:1)
由于竞争条件,在多线程情况下是不安全的。
但是你真的需要对地图进行延迟初始化吗?如果无论如何都要使用地图,似乎你可以为它做急切的初始化..
答案 4 :(得分:1)
上面的代码不像其他人提到的那样是线程安全的,你的地图可以初始化两次。您可能会尝试通过添加一些同步来尝试修复上面的代码,这称为“双重检查锁定”,Here is an article描述了此方法的问题,以及一些可能的修复。
最简单的解决方案是将字段设置为单独类中的静态字段:
class HelperSingleton {
static Helper singleton = new Helper();
}
它也可以使用volatile关键字修复,如Bill Pugh的文章所述。
答案 5 :(得分:0)
不,这个代码对多个线程使用是不安全的。
地图初始化中存在竞争条件。例如,多个线程可以同时初始化地图并破坏彼此的写入。
没有内存屏障可确保线程所做的修改对其他线程可见。例如,每个线程都可以使用自己的映射副本,因为它们从不“看到”另一个线程写入的值。
没有原子性可以确保在同时访问映射时保留不变量。例如,执行get()
操作的线程可能会进入无限循环,因为另一个线程在同时put()
操作期间重新处理了存储桶。
答案 6 :(得分:0)
如果您使用的是Java 6,请使用ConcurrentHashMap