6

本文讨论Java 的“同步”关键字。

  ...
  private int foo;
  public synchronized int getFoo() { return foo; } 
  public synchronized void setFoo(int f) { foo = f; }

如果调用者想要增加 foo 属性,那么执行此操作的以下代码不是线程安全的:

  ...
  setFoo(getFoo() + 1);

如果两个线程同时尝试增加 foo,结果可能是 foo 的值增加 1 或 2,具体取决于时间。

现在,我的问题:

为什么 setFoo() 上的“同步”不能阻止上面的粗体线?

4

6 回答 6

6

因为你保证没有其他人在你身边得到 foo,并且除了你之外没有其他人将 foo 设置回来,但你不能保证在你调用 get() 和你调用之间没有人设法进出(或只是进出)放()

您可以认为该代码完全等同于以下代码:

int temp = getFoo(); //safe method
temp = temp+1; //not protected here - im not holding any locks ...
setFoo(temp); //safe method
于 2013-02-27T17:52:29.420 回答
6

这是一个 check-then-act 竞争条件的例子。

可能会发生如下情况:

Thread-1 getFoo() returns 0
Thread-2 getFoo() returns 0
Thread-2 setFoo(1)
Thread-1 setFoo(1)

这意味着两个线程试图增加 foo 但它只增加一次的效果。

正如其他答案所确定的那样,将增量与同步块锁定在与 getFoo() 和 setFoo() 相同的对象上同步将防止这种竞争条件,因为线程将无法像上面那样交错。

于 2013-02-27T17:53:50.060 回答
4

这两种方法的synchronized关键字都不能使它成为线程安全的,因为一个线程可以调用getFoo,然后另一个线程可以调用getFoo,并且它们中的每一个都得到相同的结果。然后他们每个人都加一个并调用setFoo,最终结果是foo只增加一次,而不是两次。正如您的文章所指出的,这是一种竞争条件

为了使其线程安全,读取和写入必须一起在同一个同步块中,没有单独的 get 和 set 方法。

public synchronized void addFoo(int addend)
{
   foo += addend;
}
于 2013-02-27T17:52:42.280 回答
1

您的代码中的主要陷阱是它可能getFoo会被称为 "inside" setFoo。有点儿

setFoo(){
   //getFoo();
   //...
}

这是不正确的,因为实际上getFoo是在调用之前调用setFoo的。这是显示它的示例:

public static int foo(int i) {
    System.out.print("FOO!");
    return i;
}

public static int bar(int i) {
    System.out.print("BAR!");
    return i;
}

public static void main(String[] args) throws Exception {
    System.out.println(foo(bar(1)));
}

输出:

BAR!FOO!1

如您所见bar,之前调用过foo. 因此,在您的情况下,可能会调用两个(或更多)线程getFoo,它们将在调用之前返回当前值setFoo。在这种情况下,它们都将具有相同的值,假设为 0,当它们调用时,它们都setFoo将其设置为 1。

于 2013-02-27T18:16:57.840 回答
0

你不能用

   private volatile int foo;

或原子http://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

于 2013-02-27T18:18:10.970 回答
0

这段代码有帮助吗?

class C {
  private int foo;
  public int getFoo() { return foo; } 
  public void setFoo(int f) { foo = f; }
}

C myC = new C();
synchronized(myC) {
  int foo = myC.getFoo();
  myC.setFoo(foo + 1);
}
println(myC.foo);
于 2013-02-27T18:54:30.813 回答