“Java线程”;同步的;

“Java线程”;同步的;,java,thread-safety,synchronized,Java,Thread Safety,Synchronized,这里讨论Java的“synchronized”关键字 如果调用方希望增加foo属性,则执行此操作的以下代码不是线程安全的: ... setFoo(getFoo() + 1); 如果两个线程试图同时增加foo,结果可能是foo的值增加一个或两个,具体取决于时间。 现在,我的问题是: 为什么setFoo()上的“synchronized”不能阻止上面的粗体显示 线路 因为你可以保证没有其他人在你身边得到foo,也没有其他人在你旁边把foo放回去,但是你不能保证没有人能够在你调用get()和

这里讨论Java的“synchronized”关键字

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

  ...
  setFoo(getFoo() + 1);
如果两个线程试图同时增加foo,结果可能是foo的值增加一个或两个,具体取决于时间。

现在,我的问题是:

为什么setFoo()上的“synchronized”不能阻止上面的粗体显示 线路


因为你可以保证没有其他人在你身边得到foo,也没有其他人在你旁边把foo放回去,但是你不能保证没有人能够在你调用get()和你调用set()之间来回走动(或者只是在中间)

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

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

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

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

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

这是一个先检查后行动竞赛条件的示例

可能会出现如下情况:

Thread-1 getFoo() returns 0
Thread-2 getFoo() returns 0
Thread-2 setFoo(1)
Thread-1 setFoo(1)
这意味着有两个线程试图递增foo,但其效果是只递增一次


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

代码中的主要陷阱是,
getFoo
似乎将被称为“内部”
setFoo
。 有点

这是不正确的,因为实际上在调用
setFoo
之前调用了
getFoo
。下面是一个示例:

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
如您所见,在
foo
之前调用了
bar
。因此,在您的情况下,两个(或更多)线程可能会调用
getFoo
,这将在调用
setFoo
之前返回当前值。在这种情况下,它们都有相同的值,比如说0,当它们调用
setFoo
时,它们都会将其设置为1。

您不能使用吗

   private volatile int foo;
或者原子的

这个代码有用吗

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);

然后每个线程添加一个并调用setFoo,最终结果是foo只增加一次,而不是两次
为什么?因为每个线程将
foo
更新为相同的值。例如,它们每个都得到值2,每个加1得到3,然后每个都将值设置为3。如果我理解正确,那么您的语句不应该是
,然后每个加一并调用setFoo,最终结果是foo增加**两次**?
现在是语义问题。当然,两个线程都递增
foo
,因此它递增“两次”。但是每一次,每个线程都会将其递增到3,而不是一个线程将其递增到3,然后另一个线程将其递增到4。
volatile
不会解决它,但原子递增是一个好建议,所以重点是
myC.setFoo(1)
可以在
println(myC.getFoo())
之前被调用?
   private volatile int foo;
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);