6

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html的底部,它说:

双重检查锁定不可变对象

如果 Helper 是一个不可变对象,这样 Helper 的所有字段都是最终的,那么双重检查锁定将起作用,而不必使用 volatile 字段。这个想法是对不可变对象(如字符串或整数)的引用应该与 int 或 float 的行为方式大致相同;对不可变对象的读写引用是原子的。

mutable one的示例和解释如下:

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo { 
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null) 
      synchronized(this) {
        if (helper == null) 
          helper = new Helper();
      }    
    return helper;
    }
  // other functions and members...
  }

第一个原因,它不起作用

最明显的原因是初始化 Helper 对象的写入和对 helper 字段的写入可以完成或被认为是无序的。因此,调用 getHelper() 的线程可以看到对辅助对象的非空引用,但会看到辅助对象的字段的默认值,而不是构造函数中设置的值。

如果编译器内联对构造函数的调用,那么如果编译器可以证明构造函数不能抛出异常或执行同步,则初始化对象的写入和对辅助字段的写入可以自由地重新排序。

即使编译器不重新排序这些写入,在多处理器上,处理器或内存系统也可能重新排序这些写入,正如在另一个处理器上运行的线程所感知的那样。

我的问题是:为什么不可变类没有问题?我看不到重新排序与类是否可变的任何关系。

谢谢

4

2 回答 2

1

对于通常的对象,代码“损坏”的原因是它helper可能不为空,但指向一个尚未完全初始化的对象,如您的报价中所述。

但是,如果 Helper 类是不可变的,这意味着它的所有字段都是最终的,Java 内存模型保证它们被安全地发布,即使对象是通过数据竞争提供的(在您的示例中就是这种情况):

final字段还允许程序员在不同步的情况下实现线程安全的不可变对象。线程安全的不可变对象被所有线程视为不可变对象,即使使用数据竞争在线程之间传递对不可变对象的引用也是如此。这可以提供安全保证,防止错误或恶意代码滥用不可变类。final必须正确使用字段以保证不变性。

于 2014-10-28T15:36:31.540 回答
0

不可变类确实存在问题。在 JSR133 中对 Java 内存进行更改 ,您引用的部分是正确的。

final具体来说,影响不可变对象的更改与对关键字所做的一些更改有关。结帐http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalRight

重要的部分是:

对象的最终字段的值在其构造函数中设置。假设对象是“正确”构造的,一旦构造了对象,分配给构造函数中最终字段的值将对所有其他线程可见,而无需同步。

于 2014-10-28T15:35:24.507 回答