修改: 我已经在堆栈上找到了答案: https://stackoverflow.com/a/16280842/3319557
我遇到同步问题。我有以下两种方法:
public synchronized void incrementCounter1() {
counter++;
}
public void incrementCounter2() {
synchronized (counter) {
counter++;
}
}
我在许多线程中测试每个(单独)。第一种方法表现如预期,但第二种方法(incrementCounter2)错误。有人可以解释为什么会这样吗?
我认为这个方法设计得很好,因为我在Java Concurrency in Practice中发现了类似的东西。从这本书中摘过:
@ThreadSafe
public class ListHelper<E> {
public List<E> list = Collections.synchronizedList(new ArrayList<E>());
...
public boolean putIfAbsent(E x) {
synchronized (list) {
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
}
我使用我正在修改的对象的监视器,就像在书中一样。
完整代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SynchronizationTest {
public static final int N_THREADS = 500;
public static final int N_Loops = 5000;
private Integer counter = 0;
Lock l = new ReentrantLock();
public void incrementCounter0() {
counter++;
}
public synchronized void incrementCounter1() {
counter++;
}
public void incrementCounter2() {
synchronized (counter) {
counter++;
}
}
public void incrementCounter3() {
try {
l.lock();
counter++;
} finally {
l.unlock();
}
}
private interface IncrementStrategy {
void use(SynchronizationTest t);
}
private static class IncrementingRunnable implements Runnable {
SynchronizationTest synchronizationTest;
IncrementStrategy methodToUse;
public IncrementingRunnable(SynchronizationTest synchronizationTest, IncrementStrategy methodToUse) {
this.synchronizationTest = synchronizationTest;
this.methodToUse = methodToUse;
}
@Override
public void run() {
for (int i = 0; i < N_Loops; i++) {
methodToUse.use(synchronizationTest);
}
}
}
public void test(IncrementStrategy methodToUse, String methodName) {
counter = 0;
Thread[] threads = new Thread[N_THREADS];
for (int i = 0; i < N_THREADS; i++) {
threads[i] = new Thread(new IncrementingRunnable(this, methodToUse));
threads[i].start();
}
for (int i = 0; i < N_THREADS; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(methodName + " diff than expected " + (counter - N_THREADS * N_Loops));
}
public void test() {
test(t -> t.incrementCounter0(), "incrementCounter0 (expected to be wrong)");
test(t -> t.incrementCounter1(), "incrementCounter1");
test(t -> t.incrementCounter2(), "incrementCounter2");
test(t -> t.incrementCounter3(), "incrementCounter3");
}
public static void main(String[] args) {
new SynchronizationTest().test();
}
}
我知道,应该使用ExecutorService,使用AtomicLong可以解决整个问题,但这不是这个问题的重点。
代码输出为:
incrementCounter0 (expected to be wrong) diff than expected -1831489
incrementCounter1 diff than expected 0
incrementCounter2 diff than expected -599314
incrementCounter3 diff than expected 0
PS。 如果我将字段添加到SynchronizationTest
Object counterLock = new Object();
并改变 incrementCounter2 to:
public void incrementCounter2() {
synchronized (counterLock) {
counter++;
}
}
然后incremetCounter2按预期工作。
答案 0 :(得分:4)
您正在同步不同的对象
incrementCounter1
在this
上同步,而incrementCounter2
在计数器Integer
对象本身上同步。
答案 1 :(得分:2)
您正在尝试使用两个锁定监视器(假设counter
是Object
,可能是Integer
?)
public class Foo {
// Uses instance of Foo ("this")
public synchronized void incrementCounter1() {
counter++;
}
public void incrementCounter2() {
// uses counter object as lock monitor
synchronized (counter) {
counter++;
}
}
}
我不确定您使用counter++
尝试实现的目标counter
类型为Integer
?
解决问题的几个选项:
AtomicInteger
ReentrantReadWriteLock
)答案 2 :(得分:0)
丑恶。
synchronized void method(...
在this
对象上进行同步。
synchronized(object) {
...
在object
上进行同步。
现在:
synchronized (counter) {
++counter;
还必须在Object上同步,但counter是基本类型,int
。
会发生什么,该计数器是以整数形式装箱的。
当counter为0 .. 127时,检索到的Integer对象每次都不同,但是共享。对于1234,创建一个新的唯一Integer对象,并且synchronized不会产生任何影响。 (整数是不可变的。)
我认为这几乎是一个语言错误,这是FindBugs找到的东西。