我知道可能的竞争条件是Read-Modify-Write,例如:
public class UnsafeSequence {
private int value;
public int getNext(){
value = value + 1;
return value;
}
}
在我的代码中,我使用的是AtomicLong:
public class Test {
private AtomicLong size = new AtomicLong();
public void test(Object myPersonalObject){
//do something
this.size.set(this.size.get() + myPersonalObject.getSize().get());
//do something
}
}
myPersonalObject.getSize()
返回AtomicLong。
问题是:这条说明this.size.set(this.size.get() + myPersonalObject.getSize().get());
会产生竞争条件吗?
答案 0 :(得分:4)
AtomicLong
保证get()
的结果将是"正确的"相应对象的内容(不是其中一个"一半"值是旧值,而另一半来自" new"值)。
当然,那是"内部" AtomicLong对象的值可以在代码中很好地改变。您首先获取其值,添加一些内容,然后回写该值。
换句话说:如果您担心自己总是set()
正确值(无论您的上下文中的含义是什么) - 那么您的实施并不能保证这一点!
因此:你可能想在这里使用普通的长片 - 而不是使用AtomicLong
你的代码提供了必要的同步来维护合同围绕这个"计数器"
答案 1 :(得分:3)
使用java.util.concurrent.atomic
考虑最初说明的手动添加
方法#1 Java:
public void test(Test myPersonalObject){
this.size.set(this.size.get() + myPersonalObject.getSize().get());
}
此方法的字节代码:
0: aload_0
1: getfield #4 = Field Test.size(Ljava/util/concurrent/atomic/AtomicLong;)
4: aload_0
5: getfield #4 = Field Test.size(Ljava/util/concurrent/atomic/AtomicLong;)
8: invokevirtual #5 = Method java.util.concurrent.atomic.AtomicLong.get(()J)
11: aload_1
12: invokevirtual #6 = Method Test.getSize(()Ljava/util/concurrent/atomic/AtomicLong;)
15: invokevirtual #5 = Method java.util.concurrent.atomic.AtomicLong.get(()J)
18: ladd
19: invokevirtual #7 = Method java.util.concurrent.atomic.AtomicLong.set((J)V)
22: return
现在addAndGet
接近
方法#2 Java:
public void test(Test myPersonalObject){
this.size.addAndGet(myPersonalObject.getSize().get());
}
字节代码制作:
0: aload_0
1: getfield #4 = Field Test.size(Ljava/util/concurrent/atomic/AtomicLong;)
4: aload_1
5: invokevirtual #5 = Method Test.getSize(()Ljava/util/concurrent/atomic/AtomicLong;)
8: invokevirtual #6 = Method java.util.concurrent.atomic.AtomicLong.get(()J)
11: invokevirtual #7 = Method java.util.concurrent.atomic.AtomicLong.addAndGet((J)J)
14: pop2
15: return
虽然addAndGet
方法产生的字节代码较少,但也避免了不一致的增量这种情况发生了,因为完全独立的事件是读取myPersonalObject.getSize()
- Line 15
1-st方法和2-nd的相应line 8
。接下来是完全单独的ladd
指令和单独set
(lines 18-19
的第一个),与addAndGet
(line 11
第二种方法相比较)。
现在考虑下面的图表,让myPersonalObject.getSize().get()
不断返回1 只是为了简化我们的示例:
方法#1设置(获取大小+获得1)
Thread 1 | Thread 2 | size
| | 0
get 1 | get 1 | 0
get size | | ← 0
| get size | ← 0
ladd size +1 | | 0
| ladd size +1 | 0
set back | | → 1
| set back | → 1
方法#2 addAndGet(获取1)
Thread 1 | Thread 2 | size
| | 0
get 1 | | 0
| get 1 | 0
addAndGet size+1 | | → 1
| addAndGet size+1 | → 2
答案 2 :(得分:2)
即使您使用的是原子组件,您的this.size.set(this.size.get() + myPersonalObject.getSize().get());
也不会接近原子操作。
当您执行size.get()
并开始使用myPersonalObject.getSize().get()
添加时,size
的原始值可以更改。
将计算值分配回size
会使更改消失。它有点类似于这个单线程代码
int size = 2;
int myObj = 3;
int temp = size + myObj; // The addition, keeping the result cached
size++; // Another "thread" making a change
size = temp; // The assignment, making the i++ disappear
正如我在评论中所说,请改用size.addAndGet(myPersonalObject.getSize().get())
。不要担心返回值,你不需要 将它用于任何事情。
答案 3 :(得分:2)
这不是原子声明:
this.size.set(this.size.get() + myPersonalObject.getSize().get());
this.size.set
,this.size.get()
和myPersonalObject.getSize().get()
是原子语句,但链接原子语句不会像同步语句那样组合并跨越原子性:
synchronized(this){
this.size.set(this.size.get() + myPersonalObject.getSize().get());
}
因此在您的示例中,this.size.get()
可以由线程执行,线程可以暂停,另一个线程可以执行:
this.size.set(this.size.get() + myPersonalObject.getSize().get());,
没有暂停。
然后,当第一个线程恢复时,使用的this.size.get()
值可能不会反映this.size.get()
的实际值。
答案 4 :(得分:0)
是的,这将是一场竞争条件。原子实现保证只有一个Thread可以访问该字段。这并不意味着它将反映另一个线程可能改变的值。例如,Thread 1
获取值时Thread 2
获取值,但执行set
方法的速度更快。之后你没有预期的结果。
特别针对这些情况,有一种getAndSet方法。它使用比较和交换机制来确保检索到的值实际上是当前值。
答案 5 :(得分:0)
@Kayaman给出了正确的,有益的答案,但也可以通过以下方式实现无锁实施:
public void test(Object myPersonalObject){
//do something
long currentValue;
long newValue;
do {
currentValue = this.size.get();
newValue = currentValue + myPersonalObject.getSize().get();
} while (!size.compareAndSet(currentValue, newValue));
//do something
}