java - 保证在多线程环境中迭代所有值

标签 java multithreading

我有一个Set具有任何类型的值和 AtomicBoolean指示该类提供的功能是否正在运行。

private Set<Object> set = new HashSet<>();
private AtomicBoolean running;

现在,我有两种方法,其中一种是将对象添加到集合中,另一种用作我的类的设置方法。

public void start() {
    // ...

    set.foreEach(someApi::addObject);

    // ...
    running.set(true);
}

public void addObject(Object o) {
    set.add(o);
    if(running.get()) {
        someApi.addObject(o);
    }
}

但是,该代码有一个问题。如果该方法是从另一个线程调用的,而start方法正在迭代集合 running仍然是false 。因此,该对象不会添加到 api。

问题:我如何保证集合中的所有对象以及添加 addObject 的对象是否会被添加到 api 一次?

我的想法:

  • 使用锁并阻止addObject method 如果安装程序当前正在向 api 添加方法(或者使这两个方法都为 synchronized ,这会稍微降低性能)

最佳答案

Question: How can i guarantee that all objects in the set and objects added with addObject will be added to the api exactly one time?

这里你必须小心,因为这接近 ole "double check locking bug" .

如果我理解你的问题,你想:

  1. 在调用 start() 之前,将传入 addObject(...) 的对象放入集合中。
  2. 然后,当调用 start() 时,对集合中的对象调用 API 方法。
  3. 如果在调用 start() 期间添加了其他对象,则处理重叠
  4. 对传递给 addObject(...) 的所有对象调用该方法一次且仅一次。

令人困惑的是您的 API 调用也被命名为 addObject()。我认为这与代码示例中的 addObject(...) 方法不同。我将在下面将其重命名为 someApiMethod(...) 以表明它不会递归。

不幸的是,最简单的方法是在每个方法中都有一个 synchronized block :

private final Set<Object> set = new HashSet<>();
public void start() {
    synchronized (set) {
        set.forEach(someApi::someApiMethod);
    }
}
public void addObject(Object obj) {
    synchronized (set) {
            if (set.add(obj)) {
                someApi.addObject(obj);
            }
        }
    }
}

为了使其更快,需要使用更复杂的代码。您可以做的一件事是使用 ConcurrentHashMap 和 AtomicBoolean running 。像这样的东西:

private final ConcurrentMap<Object, Object> map = new ConcurrentHashMap<>();
private final Set<Object> beforeStart = new HashSet<>();
private final AtomicBoolean running = new AtomicBoolean();
public void start() {
    synchronized (beforeStart) {
       for (Object obj : beforeStart) {
            doIfAbsent(obj);
       }
       running.set(true);
    }
}
public void addObject(Object obj) {
    if (running.get()) {
        doIfAbsent(obj);
    } else {
        synchronized (beforeStart) {
            // we have to test running again once we get the lock
            if (running.get()) {
                doIfAbsent(obj);
            } else {
                beforeStart.add(obj);
            }
        }
    }
}
private void doIfAbsent(Object obj) {
    if (map.putIfAbsent(obj, obj)) {
        someApi.someApiMethod(obj);
    }
}

这非常复杂,并且可能不会更快,具体取决于您的 HashMap 有多大和其他因素。

关于java - 保证在多线程环境中迭代所有值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51370387/

相关文章:

c++ - 为什么一个子线程的执行时间比整个应用程序的执行时间多

用于异步服务器的 Java NIO 和多线程

c# - 如何在 C# 中正确多线程运行时调用的 DLL

c - 测量功能前后的挂钟时间

java - 复杂的 JUnit 测试用例

java - 故障排除和修复死锁

java - 如何使用mockito抛出JsonProcessingException

python - PyTest - 将每个测试作为 Mutlitprocessing 进程运行

java - 无法在 Recyclerview 的 Android Studio 中解析符号 'get'

Java 仅使用 Java api 实现不带 Servlet 的字节范围服务