不可变对象是线程安全的,但为什么呢?

时间:2012-02-15 23:57:15

标签: java

如果一个线程通过创建其对象来创建填充不可变类的引用变量,并且在第一个线程完成之前第二次启动另一个线程并创建不可变类的另一个对象,则不会是不可变类用法是线程不安全吗? 创建一个不可变对象也表示所有字段都标记为final .....“如果对新创建的实例的引用从一个线程传递到另一个线程而没有同步,则可能需要确保正确的行为” 他们是否试图说其他线程可能会将引用变量重新指向不可变类的其他对象,那么线程将指向不同的对象而使状态不一致?

7 个答案:

答案 0 :(得分:13)

实际上,不可变对象始终是线程安全的,但它的引用可能不是。

困惑??你不应该: -

回到基本: 线程安全只是意味着两个或多个线程必须协同工作在共享资源或对象上。他们不应该忽略任何其他线程所做的更改。

现在String是一个不可变类,只要线程试图改变它,它就会最终创建一个新对象。因此,即使是相同的线程也无法对原始对象进行任何更改。谈论另一个线程就像去太阳,但这里的问题是,通常我们使用相同的旧引用指向新创建的对象。

当我们执行代码时,我们仅使用引用来评估对象中的任何更改。

声明1: String str =“123”; //最初将字符串共享到两个线程

声明2: str = str +“FirstThread”; //由第一个线程执行

声明3: STR = STR + “SecondThread”; //由第二个线程执行

现在由于没有同步,volatile或final关键字告诉编译器跳过使用其智能优化(任何重新排序或缓存的东西),这段代码可以按以下方式运行。

  1. 加载Statement2,所以str =“123”+“FirstThread”
  2. 加载Statement3,所以str =“123”+“SecondThread”
  3. Store Statement3,so str =“123SecondThread”
  4. Store Statement2,so str =“123FirstThread”
  5. 最后是参考str =“123FirstThread”中的值,如果我们假设幸运的是我们的GC线程正在休眠,那么我们的不可变对象在我们的字符串池中仍然不存在。

    因此,Immutable对象始终是线程安全的,但它们的引用可能不是。为了使它们的引用成为线程安全的,我们可能需要从synchronized块/方法中访问它们。

答案 1 :(得分:9)

  

不可变对象是线程安全的,但为什么呢?

不可变对象是一旦构造完就不再被修改的对象。如果另外,不可变对象只有在构造完成后才能被其他线程访问,并且使用适当的同步完成,所有线程都将看到对象的相同有效状态。

  

如果一个线程通过创建其对象来创建填充不可变类的引用变量,并且在第一个线程完成之前第二次启动另一个线程并创建不可变类的另一个对象,则不会是不可变类用法是线程不安全吗?

没有。是什么让你这么想的?对象的线程安全性完全不受您对同一类的其他对象所做的操作的影响。

  

他们是否试图说其他线程可能会将引用变量重新指向不可变类的其他对象,那么线程将指向不同的对象而使状态不一致?

他们试图说,无论何时将某个东西从一个线程传递到另一个线程,即使它只是对不可变对象的引用,也需要同步线程。 (例如,如果通过将引用存储在对象或静态字段中将引用从一个线程传递到另一个线程,则该对象或字段由多个线程访问,并且必须是线程安全的)

答案 2 :(得分:6)

除了已经发布的其他答案,一旦创建了不可变对象,它们就无法进一步修改。因此,它们基本上是 只读。

众所周知,只读的东西总是线程安全的。即使在数据库中,多个查询也可以同时读取相同的行,但是如果要修改某些内容,则需要对其进行独占锁定。

答案 3 :(得分:6)

线程安全是数据共享的安全性,因为在您的代码中,您可以根据对象所拥有的数据做出决策,因此它的完整性和确定性行为至关重要。即

想象一下,我们在两个线程中有一个共享的布尔实例变量,这两个线程即将执行一个具有以下逻辑的方法

  • 如果flag为false,则打印“false”,然后将标志设置为true。
  • 如果flag为true,则打印“true”,然后将标志设置为false。

如果在单个线程循环中连续运行,您将获得一个确定性输出,如下所示:

false - true - false - true - false - true - false ...

但是,如果你用两个线程运行相同的代码,那么,输出的输出不再是确定性的,原因是线程A可以唤醒,读取标志,看到它是假的,但在它之前可以做任何事情,线程B醒来并读取标志,这也是假的!!所以两者都会打印错误......这只是我能想到的一个有问题的场景......正如你所看到的,这很糟糕。

如果您取消等式的更新,问题就消失了,只是因为您消除了与数据同步相关的所有风险。这就是为什么我们说不可变对象是线程安全的。

重要的是要注意,不可变对象并不总是解决方案,您可能需要在不同线程之间共享数据,在这种情况下,有许多技术超出了普通同步,并且可以在应用程序的性能方面做出很大的改变,但这是一个完全不同的主题。

不可变对象对于保证我们确定不需要更新的应用程序区域不会更新非常重要,因此我们确信我们不会遇到多线程问题

你可能有兴趣看看几本书:

这是最受欢迎的:http://www.amazon.co.uk/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601/ref=sr_1_1?ie=UTF8&qid=1329352696&sr=8-1

但我个人更喜欢这个:http://www.amazon.co.uk/Concurrency-State-Models-Java-Programs/dp/0470093552/ref=sr_1_3?ie=UTF8&qid=1329352696&sr=8-3

请注意,多线程可能是任何应用程序中最棘手的方面!

答案 4 :(得分:0)

两个线程不会创建相同的对象,因此没有问题。

关于“可能有必要确保......”,他们所说的是,如果你不把所有领域都归结为最终,你必须自己确保正确的行为。

答案 5 :(得分:0)

不变性并不意味着线程安全。从某种意义上说,即使在创建后,也可以更改对不可变对象的引用。

//No setters provided
class ImmutableValue
{

     private final int value = 0;

     public ImmutableValue(int value)
     {
          this.value = value;
     }

     public int getValue()
     {
          return value;
     }
}

public class ImmutableValueUser{
  private ImmutableValue currentValue = null;//currentValue reference can be changed even after the referred underlying ImmutableValue object has been constructed.

  public ImmutableValue getValue(){
    return currentValue;
  }

  public void setValue(ImmutableValue newValue){
    this.currentValue = newValue;
  }

}

答案 6 :(得分:-3)

不变性不能确保线程安全,如果线程只是访问不可变对象来读取,那么你不必采取额外的预防措施来确保数据是一致的,但如果线程可以操纵数据那么你必须使用一些安全的方法在线程之间共享数据,以便每个线程看到一致的数据。