【问题标题】:Is synchronized block always needed with synchronized collections?同步集合是否总是需要同步块?
【发布时间】:2017-01-12 18:36:41
【问题描述】:

考虑到:

  • Collections.synchronizedMap(new LinkedHashMap<>());
  • 主循环Thread
    • 在上面的地图上执行计划任务(来自阻塞队列)。
      • 任务基本上可以是您可以使用 map 执行的任何操作(还可以迭代和更新内部的值)。
  • 多个循环“迷你”线程
    • 这需要不时查找地图的当前元素(仅迭代,无版本)。

现在 - current 是这里的关键字 - 我需要直接流动 - 不能使用快照。

根据文档:(但我不确定我能做什么)

当迭代任何集合视图时,用户必须在返回的地图上手动同步。

我真正需要的是每当地图被主线程修改或迭代时,没有一个“迷你”线程可以访问它(锁定)。

同时,当主线程不接触所述地图时 - 所有“迷你”线程都可以访问他们喜欢的地图(但他们不会以任何方式修改它 - 只是迭代/读取)。

但是现在 - 当主线程想要修改 map 而“mini”s 正在迭代它时会发生什么?

这是否意味着我需要尽可能将地图放在同步块中?

ReadWriteLock 在这里有帮助吗?请记住,查找“迷你”线程不能是快照。

【问题讨论】:

    标签: java synchronized


    【解决方案1】:

    Collections.synchronizedMap() 将同步对地图的每个方法的访问,但是 Iterator 上的方法都是单独调用的,这就是为什么必须在地图上同步迭代。

    同步是一种独占锁,因此只有一个线程可以将映射作为时间进行迭代。如果您有许多阅读器(迭代器)并希望它们并行执行,则绝对应该使用ReadWriteLock 替换同步。并发实现对您来说不是一个可行的选择,因为您说“不能使用快照”。

    这意味着,不要使用Collections.synchronizedMap() 包装LinkedHashMap。相反,创建一个ReentrantReadWriteLock,并且总是获取关于地图的任何使用的锁,使用readLock()供读者使用(迭代,get()containsKey(),等),writeLock() 用于变异器(put()remove()、使用remove() 进行迭代等)。

    【讨论】:

    • 关于 ReentrantReadWriteLock - 我过去使用过它,但我不确定我提到的那个“快照”部分。读/写锁实际上不会创建这样的快照吗?我的意思是 - 如果多个“迷你”阅读器正在迭代并且作者(主线程)添加一些东西到地图会发生什么?跑步时不会错过吗?
    • ReentrantReadWriteLock 不创建快照。它只是选择允许运行哪个线程的机制。多个线程可以同时拥有读锁,但只有在线程上才能拥有写锁。基本上,当读者拥有它时,作者无法获得锁定,反之亦然。
    • @Ernio Writer 线程无法在读者迭代时写入。这就是使用锁的全部意义所在。由于读取器线程在迭代期间维护读锁,因此写入器线程必须等到迭代完成,然后才能被允许对映射进行写访问。不涉及快照。
    • 这正是我所需要的。 :P
    • @Ernio 您可能应该将锁设为“fair”,以防止多个迭代线程“占用”地图。
    【解决方案2】:

    如果您需要对Map 的独占写入权限,那么您应该进行同步。你不需要使用Collections.synchronizedMap(),只要确保你总是在同一个对象上同步:

    private final Map<Whatever> myMap = new LinkedHashMap<>();
    
    /* Your writer thread */
    public void run()
    {
        synchronized (myMap) {
           /* Write, iterate, whatever */
        }
    }
    
    /* Your reader thread */
    public void run()
    {
        synchronized (myMap) {
           /* Read stuff */
        }
    }
    

    只有一个线程可以同时进入给定对象的synchronized 块。因此,您的编写线程和读取线程永远不会同时在您的地图上工作。

    确保在 synchronized 块内做尽可能少的工作,并在完成后立即离开以减少争用。

    请记住,同步(序列化)对Map(或任何对象)的访问与使用Collections 上的方法返回的同步包装器完全不同。仅仅因为您使用由Collections.synchronizedMap() 返回的Map 并不意味着您的代码是线程安全的。这同样适用于ConcurrentHashMap。仅仅因为它的名称中有Concurrent,并不意味着您不能以线程不安全的方式使用它。

    【讨论】:

    • 当您忙于研究并发软件包时,您忘记了基本解决方案。
    • 随意使用Lock,但同步可能就足够了。如果发现争用较多,可以切换到其他并发控制机制。
    • Collections.synchronizedMap() 只是一种使用 synchronized 块“自动”包装地图上所有方法的方法。不使用Collections.synchronizedMap() 只是意味着你必须自己做,这意味着:1)更多代码。 2)容易出错(你可能会忘记这样做)。 --- 因此,我认为这个答案不是这个问题的好答案。
    • 同样的缺点也适用于使用 RWL。手动同步 Map 操作与使用方法已同步的 Map 不同。
    • 没错,对于ReadWriteLock,您没有太多选择,但您的回答根本不是关于RWL,而是提倡手动synchronized 阻止使用synchronizedMap(),并且那是我认为答案不好的原因。
    【解决方案3】:

    这是否意味着我需要尽可能将地图放在同步块中?

    不是在任何可能的地方,但只有当您在该集合上获得 iterator 时。 因为除了 iterator() 之外,当您使用 synchronizedMap() API 请求同步集合时,所有其他方法都会为您同步。

    例如如果您想获取集合的大小,则无需在同步块中调用 yourmap.size() ,因为装饰器会为您处理同步。

    您可以参考Collections.java进一步参考。

    【讨论】:

      【解决方案4】:

      是的,您需要以某种方式进行同步以避免在迭代时同时进行修改。

      如果写操作的频率远低于读操作,那么ReadWriteLock 可能是适合您的工具。

      否则,synchronized-blocks 可能是实现这一目标的最简单方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-17
        • 1970-01-01
        • 2019-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多