1

我对不可变对象有点傻。我的想法是它们在多线程环境中很有帮助,并且根据定义是线程安全的。

但是下面的代码似乎正好相反!

public class ImmutableTest {

    volatile ImmutableObject obj;

    public static void main(String[] args) throws InterruptedException {
        new ImmutableTest().execute();
    }

    private void execute() throws InterruptedException
    {
        obj = new ImmutableObject(0);
        ExecutorService exec = Executors.newFixedThreadPool(50);    
        for(int i = 0; i < 50; i++){
            exec.execute(new ImmutableRunnable(this));
        }
        Thread.sleep(5000);
        exec.shutdown();
        obj.print();
    }
}


public class ImmutableRunnable implements Runnable {
    ImmutableTest test;

    ImmutableRunnable(ImmutableTest immutableTest) {
        this.test = immutableTest;
    }

    public void run() {
        this.test.obj = new ImmutableObject(this.test.obj.getValue());
}

}

public final class ImmutableObject {

    private int n;

    public ImmutableObject(int newValue) {
        this.n = newValue;
        if(Math.random() > .5){
            try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
                e.printStackTrace();
        }
        }
    }

    public void print() {
        System.out.println(n);
    }

    public int getValue() {
        return n;
    }
}

在线程安全的环境中,我预计结果为 50。但这种情况并非如此。如果您从 ImmutableObject 构造函数中取消随机睡眠,您将获得 50。

那么结论是什么?不可变对象是线程安全的“如果构造函数足够快”?或者(更有可能)我误解了什么?

抱歉,我没有正确解释我的疑问。这不是一个同步问题,我学习了如何同步方法和使用锁。这个问题涉及不可变对象及其与多线程的关系。我到处读到不可变对象是线程安全的,因此它可以在线程之间共享而无所畏惧。但恕我直言,这根本不是真的!!!那么您能否提供一个示例,其中不可变对象在不同线程之间共享并且它的使用不需要同步?多谢你们。

4

4 回答 4

3

你误会了什么。

不可变对象让您可以安全地共享对象并在不使用锁的情况下读取任何方法/字段。

在这种情况下,您依赖于可变 obj引用以某种方式进行同步 - 情况并非如此。

不变性只是一个无法修改的对象。修改状态后,您需要执行一些同步以确保您想要的实际发生。例如,整个“通过读取 obj 设置 obj”调用需要是独占的,并且以串行方式发生。

于 2012-09-28T01:38:14.497 回答
3

ImmutableRunnable.run() 不是线程安全的:

  • 它读取 obj 中的值
  • 它创建一个新的 ImmutableObject
  • 它更新 obj

显然,如果当时正在执行这些步骤,那么您可能会遇到问题。问题不在于不可变对象,而是您在没有适当锁定的情况下对 obj 进行变异。

解决这个问题的最简单方法是在 ImmutableTest 上进行同步。

于 2012-09-28T01:41:25.537 回答
1

正如 Yann Ramin 所说,不可变对象可以让您安全地共享和使用该对象,而无需使用锁。

但是,您在这里测试的是写入objclass 字段的线程安全性ImmutabletestImmutableTest不是不可变的,因此不是线程安全的。

于 2012-09-28T10:19:04.067 回答
1
  1. 您提供的代码将始终打印 0。这仅仅是因为您正在复制值并且从不更改它,所以它只能是 0 的起始值。
  2. ImmutableObject 不是不可变的……不可变对象只有 final 字段,这是保证状态发布不完整的原因。不过,这对您在此处执行的代码没有任何影响。
  3. 如果我通过使 ImmutableObject 或 ImmutableRunnable 增加值来将您的代码修复为我认为您想要的,那么您将遇到上述问题,也就是说 volatile 保证可见性/排序但不保证原子性。为了使您的代码在不同步的情况下正常工作,您可以使用 AtomicReference.compareAndSet 来确保被替换的 ImmutableObject 是您用作下一个对象的基础的那个。例如:

    AtomicReference obj = new AtomicReference();// was volatile obj
... change obj access in execute to match
... replace run method with following:
    ImmutableObject curr;
    do{
       curr = this.test.obj.get();
    }while(!this.test.obj.compareAndSet(curr, new ImmutableObject(curr.getValue()+1)));

于 2013-01-06T21:28:17.957 回答