纯线程安全的Java类

时间:2014-08-15 20:29:57

标签: java multithreading concurrency

我最近正在阅读“Java Concurrency in Practice”一书。它给出的“安全发布”的一个例子是在构造期间初始化私有int字段n,并且如果从另一个线程调用,则通过公共方法对该字段n ==“expect value”的后续断言仍然可能失败。这让我感到担心,假设所有私有字段只初始化一次,我们仍然必须将它们标记为volatile或将它们包装到ThreadLocal中,或者甚至使用AtomicReference来获得纯线程安全的java类,因为这些私有字段,虽然在外面看不到,但绝对可以被其他线程调用的方法引用。

编辑:只是要清楚 - 断言失败,因为调用线程看到n的陈旧值,即使它已在构造期间设置。这显然是一个内存可见性问题。问题是,对n的同步是否值得开销,因为它只是初始化一次,作为私有字段,作者可以确保它不会再次更改。

4 个答案:

答案 0 :(得分:2)

此特定案例已在JSR 133: Java Memory Model and Thread Specification中正确记录。它甚至还有一个与您的问题完全匹配的专用代码示例页面14第3.5节最终字段

总结:

  • 在该对象完全初始化后只能看到对象引用的线程可以保证看到该对象的最终字段的正确初始化值。
  • 无法保证非最终字段

这意味着您必须确保在线程中创建对象与在另一个线程中使用之间发生之前发生。您可以使用synchronized,volatile或任何其他方法强制执行之前发生的事情。

因为你在另一个评论中说这个字段只是在施工期间设置的,所以我会把它作为...最终的。此外,线程之间的这种共享对象可能暗示一些设计气味;我会检查我的设计,以确保我没有创建一个过于复杂,紧密耦合,难以调试的系统。

答案 1 :(得分:1)

如果字段从未在类外部使用,使用synchronized块或同步函数包装它们的用法,则两个线程不会同时修改这些字段。

volatile关键字只是线程安全的一部分。它只会使字段的值永远不会被缓存,总是从内存中读取。举个例子。

private int myPrivateField = 0;

void someFunction() {
    while(myPrivateField ==0) {
    }
}

void otherFunction() {
    myPrivateField = 1;
}

如果从一个线程调用someFunction()并且它正在运行一段时间, 当您拨打otherFunction()时,myPrivateField的值将不会 在someFunction内“更新”,它被缓存为0作为otimization。

myPrivateField设为volatile,值始终为1 在记忆中。

例如,函数没有太大区别 同步,但没有同步,你可以读取一个值 不一致的状态。

答案 2 :(得分:0)

在构造函数之后,只保证最终字段可见。任何其他字段都需要一些可见性机制,例如synchronizedvolatile

这并不像看起来那么多负担:如果该字段不是最终字段,那么当您阅读它时,它可以被另一个字符更改。如果可以更改某个字段,则必须将最后分配的值从编写器线程传播到读取器线程,无论编写器线程是否为构造函数或设置器。

如果此字段中的更改与班级中的任何其他字段无关,则该字段应为volatile。如果该字段与类中的其他字段相关,则使用synchronized或其他更现代的锁定原语。

答案 3 :(得分:0)

不回答整个问题,但应该指出,如果要确保所有线程中更新值的可见性,使用ThreadLocal是完全错误的。请考虑以下代码:

class Test {
  private static final ThreadLocal<Integer> value = new ThreadLocal<>();

  public static void main(String[] args) throws InterruptedException {
    System.out.println("From main Thread, value is " + value.get());
    value.set(42);
    System.out.println("Value has been changed");
    Thread t = new Thread() {
      public void run() {
        System.out.println("From other Thread, value is " + value.get());
      }
    };
    t.start();
    t.join();
    System.out.println("From main Thread, value is " + value.get());
  }
}

这将输出以下内容:

From main Thread, value is null
Value has been changed
From other Thread, value is null
From main Thread, value is 42

即。另一个线程没有看到更新的值。这是因为根据定义,对ThreadLocal的值的更改已本地化为更改它的线程。

我个人的偏好是使用AtomicReference,因为这样可以避免忘记外部同步的风险;它还允许像原子比较和设置这样的东西,你不能使用volatile变量。但是,这可能不是您特定应用的要求。