阅读关于Java并发的DZone article我想知道是否有以下代码:
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 {} finally {}块。我假设ImmutableList类是一个真正不可变的数据结构,它拥有自己的数据,例如google-collections库中提供的数据。由于列表变量是易变的,基本上正在进行的是即时复制,只是跳过使用锁是不安全的?
答案 0 :(得分:6)
在这个非常具体的例子中,我认为你可以没有锁定变量重新分配。
一般来说,我认为你最好使用AtomicReference而不是volatile变量,因为内存一致性效果是相同的,意图更清晰。
答案 1 :(得分:4)
是的,这两个代码示例在并发环境中的行为方式相同。易失性字段为never cached thread-locally,因此在一个线程调用update()后,用一个新列表替换列表,然后所有其他线程上的get()将返回新列表。
但是如果你有像这样使用它的代码:
list = get()
list = list.add(something) // returns a new immutable list with the new content
update(list)
然后它将无法按预期在任何一个代码示例上工作(如果两个线程并行执行,那么其中一个线程所做的更改可能会被另一个线程覆盖)。但是如果只有一个线程正在更新列表,或者新值不依赖于旧值,则没问题。
答案 2 :(得分:1)
重新阅读之后,这些是等效的。
答案 3 :(得分:1)
如果我们谈论时间和内存可见性。易失性读取非常接近正常读取所需的时间。所以,如果你正在做get()很多,那么差别不大。执行易失性写入所花费的时间大约是获取和释放锁定的1/3。所以你的第二个建议要快一点。
大多数人建议的内存可见性是等效的,即锁定获取发生之前的任何读取在锁定获取之后的任何写入之前,类似于在任何后续写入之前发生易失性读取之前的任何读取
答案 4 :(得分:1)
为了提供所需的线程安全性,必须满足以下标准:
因为这里都满足 - 代码是线程安全的
答案 5 :(得分:0)
我认为volatile的默认同步行为并不能保证ReentrantLock行为,因此它可能对性能有所帮助。否则,我认为没关系。