2010-02-03 81 views
9

阅读本DZone article关于Java并发我在想,如果下面的代码:Java中的volatile变量问题


    private volatile List list; 
    private final Lock lock = new ReentrantLock(); 

    public void update(List newList) { 
     ImmutableList l = new ImmutableList().addAll(newList); 
     lock.lock(); 
     list = l; 
     lock.unlock(); 
    } 

    public List get() { 
     return list; 
    } 

等同于:


    private volatile List list; 

    public void update(List newList) { 
     ImmutableList l = new ImmutableList().addAll(newList); 
     list = l; 
    } 

    public List get() { 
     return list; 
    } 

在try {}最后{}块被省略简洁。我假设ImmutableList类是一个真正不可变的数据结构,它拥有自己的数据,例如google-collections库中提供的数据。由于list变量是不稳定的,基本上发生的是一个即时拷贝,跳过使用锁定不是安全的吗?

+0

我认为两者都应该表现得完全一样,但它取决于'ImmutableList'的实施。 我期望在'ImmutableList'上调用'addAll()'会抛出一个Exception,所以锁永远不会被使用。 – 2010-02-03 21:43:07

回答

6

在这个非常具体的例子中,我认为你可以没有锁定变量重新分配。

一般来说,我认为使用AtomicReference代替volatile变量会更好,因为内存一致性效果相同,意图更清晰。

0

我认为volatile的默认同步行为不能保证ReentrantLock行为,所以它可能有助于提高性能。否则,我认为这很好。

1

重新阅读后,它们是等效的。

+0

在将引用锁定到列表之前,另一个线程复制newList时出现了什么问题? – Kevin 2010-02-03 18:04:12

+0

@Clint跟在凯文的问题上,因为l是方法的本地,并且列表更新被保护,调用get()的线程是否仍然会得到一致的结果? – teto 2010-02-03 18:12:31

+0

@是的。成员变量列表未被修改,只有参考正在更新。 – Kevin 2010-02-03 18:18:10

4

是的,这两个代码示例在并发环境中的行为方式相同。易失性字段为,因此在一个线程调用update()(它将用新列表替换列表)后,所有其他线程的get()将返回新列表。

但是,如果你有使用它这样的代码:

list = get() 
list = list.add(something) // returns a new immutable list with the new content 
update(list) 

然后在任的这些代码示例(如预期它不会工作,如果两个线程做并行,然后所做的更改其中一个可能被另一个覆盖)。但是,如果只有一个线程正在更新列表,或者新值不依赖于旧值,那么没有问题。

+0

get()返回一个ImmutableList,正如我在问题描述中所述,试图修改它只会引发异常。 – teto 2010-02-03 18:35:13

+0

总是有可能创建一个新的不变列表,其中包含旧的不可变列表的元素以及更多内容 - 请参阅我的注释add()方法如何返回新列表。这就是它在函数式编程中的做法。 (如果您的示例使用google集合的ImmutableList,那么它可能没有这样的操作,但是每个函数式编程语言的库都有。) – 2010-02-03 20:58:12

+0

原文没有[现在]更新列表。这就是为什么同步收藏很少能达到你想要的效果的一部分原因。 /文章中的示例非常简单,可以轻松地由cas循环替换。 – 2010-02-04 06:11:17

1

如果我们正在讨论时间和内存的可见性。易失性读取非常接近正常读取所需的时间。所以如果你在做get()很多,那么几乎没有什么区别。执行易失性写入所需的时间约为获取和释放锁定的1/3。所以你的第二个建议要快一点。

存储可见性,因为大多数人认为是等价的,即任何读类似于任何读锁定收购之后的任何写操作之前锁定收购发生之前任何后续的写

1

以下标准之前挥发性读发生之前必须满足volatile变量以提供期望的线程安全性:

  1. 写入变量不取决于其当前值。
  2. 该变量不参与其他变量的不变量。

,因为二者都在这里会见了 - 代码是线程安全