您好Stackoverflow社区,
我有一个关于线程安全的问题。如果我有一个静态Map并用不同的Object填充它们,那么这些对象是否是线程安全的,如果我只有它们没有写入的方法呢?
我创建一个小例子:在这种情况下,getCommand线程的返回值是否安全?
如何使用JUnit测试Thread-safety?
控制器
public class CommandController {
private static Map<String, Command> commandMap = initMap();
public static Map<String, Command> initMap() {
return new HashMap<String, Command>() {{
put("A", new CommandA());
put("B", new CommandB());
}};
}
public Command getCommand(String key) {
if(commandMap.containsKey(key)) {
return commandMap.get(key);
}
return null;
}
}
抽象类
public abstract class Command {
public abstract int calc(int value);
}
命令A
public class CommandB extends Command {
@Override
public int calc(int value) {
value = value * 4;
return value;
}
}
命令B
public class CommandA extends Command {
private int ab = 5;
@Override
public int calc(int value) {
return value * ab;
}
}
答案 0 :(得分:4)
出于两个原因,这是线程安全的。在这种情况下,需要考虑两者以便具有纯线程安全性
注意: Slaks确实提出了一个好点。您应该使用最终。通常,如果您担心线程安全并且该字段既不是最终的也不是易变的,那么可能存在一些问题。虽然在这种情况下使其成为最终版本并不会使它更安全,但它只是防止将来出现线程不安全的事情(比如重新分配它)。
答案 1 :(得分:2)
是的,这是线程安全的,因为类初始化保证对使用该类的所有线程都可见,并且您的映射是“有效不可变的” - 类初始化后状态不会改变。
但是,如果从程序设置阶段中显式调用的某个静态方法初始化映射,则必须实现自己的内存屏障,以确保其他线程可以看到映射的正确状态。因此,确保在类初始化期间完全初始化了映射;这就是使这项工作的原因。
答案 2 :(得分:2)
是。 Java语言规范writes:
如果程序没有数据争用,则程序的所有执行都将显示为顺序一致。
如果对同一个变量的两次冲突访问不在before-before关系中,则会发生数据争用,并且
如果至少一个访问是写入,则对同一变量的两次访问(读取或写入)被认为是冲突的。
对映射的并发访问只读取共享状态,而读取只能与写入冲突。因此,足以证明映射的初始化发生在并发线程访问之前。
确实如此,因为初始化发生在静态字段初始化程序中,在类初始化期间处理。调用类在其声明的方法之前初始化的规范requires,并且detailed initialization procedure使用同步来确保初始化仅发生一次,并且初始化线程与访问该方法的所有其他线程同步阶级,从而建立发生在之前。
作为一种风格问题,您可能希望声明字段final以强调它仅在类加载时分配,并且对该字段的访问不需要进一步的同步。
答案 3 :(得分:1)
这是线程安全的,因为没有人可以访问您的Map
,因此无法改变它。但是,您可能希望将其设为private static final
,以确保没有内存可见性问题。
我一直这样做(但不是static
地图) - 我使用Spring来填充Map
。
答案 4 :(得分:0)
似乎线程对我安全。在CommandController中没有添加/删除任何内容。并且命令(CommandA和CommandB)没有被修改的私有变量(它们仅用于计算。
这是一个简单的例子,我想是一个(非常)更复杂的情况,所以当你的真实情况在CommandController中操作map或者当Command确实有修改过的类变量时,你会遇到并发问题