这个命令模式线程安全吗?

时间:2013-10-22 18:26:14

标签: java multithreading design-patterns

您好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;
    }
}

5 个答案:

答案 0 :(得分:4)

出于两个原因,这是线程安全的。在这种情况下,需要考虑两者以便具有纯线程安全性

  1. 地图是不可变的(因为它是只读的)。
  2. 它是用班级初始化的。 Since class initialization is thread-safe and guarantees publication, visibility is not an issue.
  3. 注意: 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确实有修改过的类变量时,你会遇到并发问题