这篇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”不会阻止上面的粗体 线?
答案 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);