Java 易失性变量是否需要同步访问?

Java 易失性变量是否需要同步访问?,java,synchronization,thread-safety,synchronized,volatile,Java,Synchronization,Thread Safety,Synchronized,Volatile,我在理解Java中的易失性变量时有点困难 我有一个参数化类,它包含一个易失性变量,如下所示: public class MyClass<T> { private volatile T lastValue; // ... other code ... } 或者我需要将空检查粘贴到同步块中吗 public void doSomething() { String someString; ... synchronized(this) {

我在理解Java中的易失性变量时有点困难

我有一个参数化类,它包含一个易失性变量,如下所示:

public class MyClass<T> {

    private volatile T lastValue;

    // ... other code ...

}
或者我需要将空检查粘贴到同步块中吗

public void doSomething() {
    String someString;
    ...

    synchronized(this) {
        if (lastValue != null) {
            someString += lastValue.toString();
        }
    }
}

我知道对于get和set这样的原子操作,不应用同步(例如,
public T getValue(){return lastValue;}
)应该可以。但是我不确定非原子操作。

volatile保证可见性(一个线程所做的更改将被其他线程看到),但它不能保证几个操作的原子性

所以是的,
lastValue
可以在
if(lastValue!=null)
someString+=lastValue.toString()之间变成
null
并且您的代码可能会抛出
NullPointerException

您可以添加同步(但需要同步对变量的所有写入访问),或者对于该简单用例,您可以使用局部变量:

public void doSomething() {
    String someString;
    T lastValueCopy = lastValue;
    if (lastValueCopy != null) {
        someString += lastValueCopy.toString();
    }
}

这在很大程度上取决于类型T的属性。在最简单的情况下,T是一个不可变的类型,也就是说,无论您读取的T值是多少,它都不会在以后更改其内部状态。在这种情况下,您不需要同步,只需将引用读入局部变量,并根据需要使用它

如果T是一个可变类型,那么您需要防止实例在使用它时更改其状态。在这种情况下,您需要特别确保T实例受其保护的同步。在此基础上进行同步通常不能确保,它不会阻止您从任何其他位置更改t的状态。特定T的正确锁必须由规则定义(并且没有任何语言元素确保您不会违反这些规则)

在特殊情况下,T是可变的,您将this定义为正确的锁,您需要同步,但不再需要volatile


也就是说,volatile与synchronized结合使用看起来很可疑。

并行处理不能保证数据更新对其来源的引用。在这种情况下,必须对作为引用对象操作的对象进行显式同步。使用synchronized,等待并通知它


有关的详细信息:

重写
someString+=lastValue.toString()as
someString+=(“”+lastValue)
允许您完全取消
null
检查。@dasblinkenlight,true,但前提是当lastValue为null时,您对结果字符串中出现的“null”一词表示满意。@IanMcLaird啊,您是对的,我忘了Java会这样做(不是.NET做的)。为什么将同步添加到空检查中需要将同步添加到针对易失性变量的原子操作中?我是否正确理解线程没有“之前”和“之后”的概念,除非它们在某个时间点同步?线程语义允许一个线程在另一个线程更新旧变量值后看到它吗?@roddyoffrozenpeas你是对的,你只需要同步所有的写操作。这是必需的,因为这里使用
synchronized
进行互斥,即确保没有线程修改这两行之间的变量。只有当所有试图写入变量的线程都需要获取该锁时,这才有效。@kutschkem:这可能发生,因为它们可以被读取线程缓存在其线程本地缓存中,这就是使用volatile的原因,如果对变量的访问不在同步块内。@RoddyOfFrozenPeas,这取决于T是什么以及您试图实现什么。如果你只是想避免NPE,那么我的方法很好。如果T是一个可变对象,并且您希望执行检查,那么根据其状态执行操作(假设您希望执行类似以下操作:
If(mutableInteger.value()!=0)result=10/mutableInteger.value()
那么我的方法就不够好了,因为即使使用共享引用的副本,底层对象也可能在两行之间从非0值变为0。在这种情况下,您确实需要一个深度克隆。@loki不一定,但当它变为可见时,它将处于一致状态。如果对象不是不可变的,当我t变为可见时,线程可能会看到具有正确构造的某些字段和具有默认值(false、0、null)的某些字段的对象。
public void doSomething() {
    String someString;
    T lastValueCopy = lastValue;
    if (lastValueCopy != null) {
        someString += lastValueCopy.toString();
    }
}