Java线程“同步”

时间:2013-02-27 17:50:25

标签: java thread-safety synchronized

这篇article讨论了Java的“synchronized”关键字。

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

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

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

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

现在,我的问题:

  

为什么setFoo()上的“synchronized”不会阻止上面的粗体   线?

6 个答案:

答案 0 :(得分:6)

因为你保证没有其他人和你一起得到foo,并且没有其他人在你身边设置foo但是你不能保证在你调用get()之间没有人设法进出(或者只是)你调用set()

您可以将该代码视为与此完全等效:

int temp = getFoo(); //safe method
temp = temp+1; //not protected here - im not holding any locks ...
setFoo(temp); //safe method

答案 1 :(得分:6)

这是一个检查 - 然后 - 行动竞争条件的例子。

可能会出现以下情况:

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

这意味着两个线程试图递增foo,但它只会增加一次。

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

答案 2 :(得分:4)

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

为了使线程安全,读取和写入必须在同一个同步块中,而不需要单独的get和set方法。

public synchronized void addFoo(int addend)
{
   foo += addend;
}

答案 3 :(得分:1)

代码中的主要陷阱是getFoo可能会在“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。

答案 4 :(得分:0)

你不能使用

   private volatile int foo;

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

答案 5 :(得分: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);