【问题标题】:How do I synchronize access to a member of a different class?如何同步访问不同类的成员?
【发布时间】:2014-02-27 22:36:12
【问题描述】:

我正在尝试弄清楚如何同步对来自不同类的同步列表的读/写访问。

一个小例子:我在一个类ListProvider 中有一个同步列表,我在另一个类Filter 中访问它。顾名思义,该类基于(无效)验证检查isInvalid 执行一些过滤。

filter 方法首先获取列表引用,然后将要删除的条目收集到一个临时列表中,以免遇到并发修改问题,最后从列表中删除条目:

public class Filter {

    ListProvider listProvider;

    ...

    public void filter() {
        List<String> listProviderList = listProvider.getList();

        List<String> entriesToRemove = new ArrayList<>();

        // collect
        for (String entry : listProviderList)
            if (isInvalid(entry)) {
                entriesToRemove.add(entry);
            }
        }

        // remove
        for (String entry : entriesToRemove) {
            listProviderList.remove(entry);
        }       
    }
}

我的问题:如何确保在filter 进行读写时没有其他线程修改列表?

如果是Filter 自己的列表,我会这样做:

synchronized(myList) {
    // collect
    // remove
}

但在这种情况下,我不确定将什么用作监视器。

【问题讨论】:

  • 所有对列表的访问必须同步。因此,如果另一个线程获得该列表,他们还需要在使用它之前对其进行同步。如果您不控制代码,这可能是不可能的。考虑用一些同步包装返回的列表。查看Collections 课程。
  • 调用listProvider.getList()时返回原始列表的副本
  • @hoaz 但是我会丢失 ListProvider#getList 和 ListProvider#setList 之间列表的所有可能更新。原始列表的一致性/正确性是最重要的,我不太关心性能。访问应该简单地发生在 FCFS 的基础上。
  • @SotiriosDelimanolis Collections 类在这里没有任何用处。它只会同步每个单独的访问,但不会确保要删除的项目尚未被另一个线程删除。整个收集无效条目并删除它们的过程必须是原子的。逐个调用同步很少有用。

标签: java multithreading thread-safety synchronized


【解决方案1】:

但在这种情况下,我不确定将什么用作监视器。

要为特定任务创建监视器,使用private final Object 是一个很好的模式:

 private final Object listUpdateLock = new Object();
 ...
 synchronized(listUpdateLock) {
    ...
 }

确保ListProviderprivate 并且对列表的所有访问都在synchronized 块内完成——即使只是从中读取,这一点很重要。

在这种情况下,您正在更新列表,您可以创建一个临时列表,然后在完成后替换它。但是,我不确定您是否可以使用 ListProvider 做到这一点。然后你就可以列出volatile

【讨论】:

  • 但是所有访问都需要使用该监视器,不是吗?假设我有十个或一千个线程在该列表上执行修改 - 如果只有一个使用 listUpdateLock,其他线程仍然可以访问该列表。
  • 是的。这就是线程编程@htorque 的本质。如果一个线程修改了列表,其他线程需要看到该修改。如果您希望更新的性能更高,可以使用 ConcurrentSkipList 之类的东西。
【解决方案2】:

在这里,您似乎应该使用lock。锁类似于同步,但它更灵活一些。它不需要周围的块,并且具有一些扩展功能。还有一些不同种类的锁。 ReentrantLock 很像同步。

public class ListProvider<E> {
    private final List<E> theList = new ArrayList<E>();
    private final ReentrantLock listLock = new ReentrantLock();

    public final List<E> lockList() {
        listLock.lock();
        return theList;
    }

    public final void unlockList() {
        listLock.unlock();
    }
}

/* somewhere else */ {

    List<E> theList = listProvider.lockList();

    /*
     * perform
     * multiple
     * operations
     *
     */

    listProvider.unlockList();
}

this 和 synchronized 的主要区别是:

  • 实际的锁定机制是隐藏的。这有利于抽象;然而,
  • 客户端必须记住显式解锁,而同步监视器出口位于块分隔符处。

有一个名为ReentrantReadWriteLock 的锁,您可能会发现它很有用,因为多个线程可以同时读取。 ReadWriteLock 解释了它是如何工作的。

【讨论】:

    【解决方案3】:

    不要遍历原始列表,而是创建它的副本以查找无效元素。完成过滤后,您可以安全地从原始列表中删除无效元素:

    public class Filter {
    
        ListProvider listProvider;
    
        ...
    
        public void filter() {
            List<String> listProviderCopy = new ArrayList<>(listProvider.getList());
    
            List<String> entriesToRemove = new ArrayList<>();
    
            // collect
            for (String entry : listProviderCopy)
                if (isInvalid(entry)) {
                    entriesToRemove.add(entry);
                }
            }
    
            listProvider.getList().removeAll(entriesToRemove);
        }
    }
    

    【讨论】:

      【解决方案4】:

      您可能想使用SynchronizedList

      List<String> list = new ArrayList<>();
      
      List<String> synch = Collections.synchronizedList(list);
      

      more

      【讨论】:

      • 事实上,listProviderList 就是这样一个结构,但是当您需要在单个同步块中执行多个操作时,这无济于事。
      • 如果您在单个同步块中执行多个操作,您可以为该块中使用的所有资源创建一个锁,因此在它结束之前没有任何东西可以从外部更改它们。
      • 这只会使每个单独的调用同步,但不会使收集无效条目然后将其删除的整个过程原子化。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      • 1970-01-01
      • 2016-06-03
      • 2012-06-13
      相关资源
      最近更新 更多