2

嗨 Stackoverflow 社区,

我有一个关于线程安全的问题。如果我有一个静态映射并用不同的对象填充它,这些对象线程安全吗,如果我只有他们不写的方法?

我创建了一个小示例:在这种情况下,getCommand 线程的返回值是否安全?

如何使用 JUnit 测试线程安全?

控制器

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;
    }
}
4

5 回答 5

4

这是线程安全的,原因有两个。在这种情况下,为了获得纯线程安全,两者都需要考虑

  1. 地图是不可变的(因为它是只读的)。
  2. 它是用类初始化的。 由于类初始化是线程安全的并保证发布,可见性不是问题。

注意: Slaks 确实提出了一个很好的观点。你应该使用final。通常,如果您担心线程安全并且该字段既不是 final 也不是 volatile,则可能有问题。尽管在这种情况下使其成为最终版本并不会使其更加线程安全,但它只是防止将来发生线程不安全的事情(例如重新分配它)。

于 2013-10-22T18:39:54.473 回答
2

是的。Java 语言规范写道

如果一个程序没有数据竞争,那么程序的所有执行都将看起来是顺序一致的。

如果对同一变量的两次冲突访问不处于先发生关系中,则会发生数据竞争,并且

如果至少有一次访问是写入,则对同一变量的两次访问(读取或写入)称为冲突。

对 map 的并发访问只读取共享状态,读取只能与写入冲突。因此,证明映射的初始化发生在并发线程访问它之前就足够了。

确实如此,因为初始化发生在静态字段初始化程序中,该初始化程序在类初始化期间进行处理。规范要求一个类在其声明的方法被调用之前进行初始化,并且详细的初始化过程采用同步来确保初始化只发生一次,并且初始化线程与所有其他访问该类的线程同步,从而建立发生-前。

作为风格问题,您可能希望将字段声明为 final 以强调它仅在类加载时分配,并且对该字段的访问不需要进一步同步。

于 2013-10-22T19:01:32.397 回答
2

是的,这是线程安全的,因为类初始化保证对所有使用该类的线程可见,并且您的映射“实际上是不可变的”——它的状态在类初始化后不会改变。

但是,如果您从程序在设置阶段显式调用的某个静态方法初始化映射,则必须实现自己的内存屏障以确保其他线程可以看到映射的正确状态。因此,请确保在类初始化期间完全初始化地图;这就是使这项工作有效的原因。

于 2013-10-22T18:44:57.257 回答
1

这是线程安全的,因为没有人可以访问您的 your Map,因此无法对其进行变异。但是,您可能只想private static final确保没有内存可见性问题。

我一直在做这种事情(但不是用static地图)——我使用 Spring 来填充Map.

于 2013-10-22T18:29:36.353 回答
0

对我来说似乎线程安全。CommandController 中的地图没有添加/删除任何内容。并且命令(CommandA 和 CommandB)没有被修改的私有变量(它们仅用于计算。

我猜这是一个(更)复杂情况的简单示例,因此当您的实际情况在 CommandController 中操作地图时,或者当 Command 确实有修改的类变量时,您将遇到并发问题。

于 2013-10-22T18:38:24.117 回答