【问题标题】:synchronized keyword does not work as expected in java [duplicate]同步关键字在java中无法按预期工作[重复]
【发布时间】:2016-07-08 07:03:38
【问题描述】:

编辑: 我已经在栈上找到了答案: 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 到:

public void incrementCounter2() {
    synchronized (counterLock) {
        counter++;
    }
}

然后 incremetCounter2 按预期工作。

【问题讨论】:

  • 虽然这是一个合适的问题,但我是否建议您提供一个简短示例,说明您认为什么“不起作用”以及预期和实际输出。如果这个问题应该帮助其他有同样问题的人,那就必须更简洁
  • 我用示例输出粘贴了我的代码。预期是 incrementCounter2 与预期 -599314 的差异将有 0 而不是随机值
  • 信息太多了。对于这样的问题,首先要了解真正的问题是什么。

标签: java concurrency synchronization synchronized


【解决方案1】:

你在不同的对象上同步

incrementCounter1 在this 上同步,而incrementCounter2 在计数器Integer 对象本身上同步。

【讨论】:

  • 我编辑了这篇文章。这些方法不会同时运行。我分别测试它们。
【解决方案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
  • 使用锁定 API(例如,ReentrantReadWriteLock)

【讨论】:

  • 我编辑了这篇文章。这些方法不会同时运行。我分别测试它们。
【解决方案3】:

丑陋的。

同步的 void 方法(...

在this 对象上同步。

  synchronized(object) {
      ...

在object上同步。

现在:

synchronized (counter) {
    ++counter;

还必须在对象上同步,但计数器是基本类型,int。 发生的情况是,计数器被装在一个整数中。

当 counter 为 0 .. 127 时,检索到的 Integer 对象每次都不同,但共享。例如 1234 创建了一个新的唯一 Integer 对象,并且同步没有任何效果。 (整数是不可变的。)

我认为这几乎是一种语言错误,是 FindBugs 可以找到的。

【讨论】:

    猜你喜欢
    • 2013-07-19
    • 1970-01-01
    • 2017-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多