AtomicLong可能的竞争条件?

时间:2017-09-01 10:33:25

标签: java atomic

我知道可能的竞争条件是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());会产生竞争条件吗?

6 个答案:

答案 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指令和单独setlines 18-19的第一个),与addAndGetline 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.setthis.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
}