java - 了解线程安全

标签 java multithreading

我试图了解这是否是线程安全的。我相信是的,但最近有人质疑这种方法的线程安全性。

假设我有一些工厂FactoryA,它为我们提供了一个实现以下接口(interface)的类:

public abstract class MyFactory {
    private ObjectA object;
    public void init(ObjectA object){
        _object = object;
    }
}

所以,我们有类似的东西

public class FactoryA extends MyFactory {
    static final createFactoryClass(String name) {
        // ...ignorning safety checks for brevity
        return MY_MAP.get(name).newInstance();
    }
}

现在,我在另一个类中有一些方法,它接受工厂并返回可能类的映射:

public class OtherClass {
    private static FactoryA _factory = new FactoryA();
    private static final Map<String, SomeClass> MY_MAP = new ImmutableMap.Builder<String, MyClass>()
    .put("foo", _factory.createFactoryClass("foo"));

    private SomeObject myMethod(ObjectA objectA, SomeObject someObject) {
        MY_MAP.get(someObject.getString()).init(objectA);
    }
}

问题是init方法是否是线程安全的。映射仅初始化一次,因此即使它存储在不可变的结构中,如果两个线程使用不同的 ObjectA 调用它,错误的类是否可能使用错误的 ObjectA

我可以通过执行以下操作来解决这个可能的问题吗?

私有(private)静态同步 myMethod(...) {}

最佳答案

局部变量始终是线程安全的,除非该变量是对共享对象的引用,即如下例所示:

static void setValue(Example obj, int val) {
    obj.val = val;
}

Example sharedObj = ...;

new Thread(() -> { setValue(sharedObj, 1); }).start();
new Thread(() -> { setValue(sharedObj, 2); }).start();

不过,在该示例中,使用 sharedObj 本身并不安全,而是我们使用该引用同时更改 sharedObj.val 的状态。

如果我们有两个线程引用不同的对象:

Thread threadA = new Thread(() -> {
    Example objA = ...;
    setValue(objA, 1);
});
Thread threadB = new Thread(() -> {
    Example objB = ...;
    setValue(objB, 2);
});
threadA.start();
threadB.start();

JVM 不会感到困惑并通过,例如threadA 的对象到 threadBsetValue 调用,反之亦然。这听起来像是你在问的问题,但这不会发生。每个线程都有自己的调用堆栈,并且每个线程对 setValue 的调用都会在其自己的线程上打开一个新的堆栈帧。换句话说,threadA 调用 setValue,它使用自己的局部变量在 threadA 上创建一个堆栈帧,threadB 调用 setValue,它使用自己的局部变量在 threadB 上创建一个堆栈帧。

还有一个单独的问题,即您的 init 方法所做的更改可能不会被其他线程看到。例如,假设您有一个 Thread thread1,其工作是将对象传递给 init 来初始化对象,然后将这些对象传递给其他某个 Thread thread2thread2 会看到 initthread1 上的对象所做的更改吗?答案是否定的,如果没有某种内存同步,thread2 可能看不到 thread1 调用 init 期间所做的更改。

不过,使 myMethod 同步可能并不能解决这个问题。相反,thread1thread2 需要某种方式进行通信,例如共享监视器对象或锁。

我猜你正在暗示第三个问题,这是关于 map 的初始化的。如果映射仅在静态初始化程序期间构建,那么它是线程安全的,因为 class initialization is performed under synchronization和:

An implementation may optimize this procedure by eliding the lock acquisition [...] when it can determine that the initialization of the class has already completed, provided that, in terms of the memory model, all happens-before orderings that would exist if the lock were acquired, still exist when the optimization is performed.

换句话说,即使 JVM 认为某个特定类已经初始化已经有一段时间了,并且决定读取该字段而不尝试获取初始化锁,静态初始化也能保证被其他线程看到。

关于java - 了解线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43837264/

相关文章:

java - Drools - 检索输出对象

java - 启动 Spring 批处理作业

java - 在 ActionScript3 中创建对象相等 "HashMap"作为 java HashMap

带线程的代码比没有线程花费的时间更长

java.lang.Thread.State : BLOCKED (on object monitor)

java - 在 Java 8 流中,如何过滤掉不是 Enum 有效值的字符串?

java - 获取图像中文本的像素位置

java - join() 在 Java 中如何工作?能保证在main()之前执行吗?

c++ - 如何让一个 pthread 在另一个线程正在等待 C++ 中的信号量时继续?

c - 并行二分查找的性能比串行版本差