代码之家  ›  专栏  ›  技术社区  ›  Glains

保证在多线程环境中遍历所有值

  •  0
  • Glains  · 技术社区  · 8 年前

    我有一个 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中一次吗?

    我的想法:

    • 用锁堵住 添加对象 方法,如果安装程序当前正在向API添加方法(或使两个方法 synchronized ,这将略微降低性能
    1 回复  |  直到 8 年前
        1
  •  1
  •   Gray droiddeveloper    8 年前

    问:我如何保证集合中的所有对象和添加了addobject的对象都将一次准确地添加到api中?

    你在这里要小心,因为这里离Ole很近 "double check locking bug" 是的。

    如果我明白你的问题,你想:

    1. 将传递到的对象排队 addObject(...) 在片场 之前 对…的呼唤 start() 是的。
    2. 那么当 开始() 调用,调用集合中对象的api方法。
    3. 如果添加了其他对象,则处理重叠 在期间 对…的呼唤 开始()
    4. 只对传递给的所有对象调用一次方法 添加对象(…) .

    令人困惑的是,您的api调用也被命名为 addObject() 是的。我想这和 添加对象(…) 方法。我在下面把它改名为 someApiMethod(...) 以证明它不是递归的。

    不幸的是,最简单的方法是 synchronized 在每个方法中阻止:

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

    这是相当复杂的,而且可能不会更快,这取决于哈希映射的大小和其他因素。