【问题标题】:Java ArrayList's set thread-safetyJava ArrayList 的设置线程安全
【发布时间】:2018-10-29 15:57:17
【问题描述】:

我有一个用例,其中多个线程可以读取和修改 ArrayList,其中这些布尔值的默认值为 True。

线程可以进行的唯一修改是将 ArrayList 的一个元素从 True 设置为 False。

所有线程也会同时读取 ArrayList,但可以读取过期值。

注意: ArrayList 的大小在 ArrayList 的整个生命周期内都不会改变。

问题:

是否有必要在这些线程之间同步 ArrayList?我正在做的唯一同步是将 ArrayList 标记为易失性,这样对它的任何更新都会从线程的本地内存刷新回主内存。这够了吗?

这是一个关于线程如何使用这个 ArrayList 的示例代码

myList 是有问题的 ArrayList,其值被初始化为 True

if (!myList.get(index)) {
   return;
} else {
  // do some operations to determine if we should
  // update the value of myList to False.
  if (needToUpdateList) {
      myList.set(index, False);
   }
}

更新

我之前说过这些线程不关心过时的值,这是真的。但是,我有另一个线程只读取这些值并执行一个最终操作。该线程确实关心陈旧性。同步要求有变化吗?

除了每次更新都需要同步之外,还有更便宜的方法来“发布”更新的值吗?我正在尝试尽可能减少锁定。

【问题讨论】:

  • Volatile 确保 变量 的可见性,而不是变量持有的对象。因此,如果您初始化此列表一次,并尝试使用 volatile 来保证元素的可见性,那么它并没有按照您的想法进行
  • 您介意详细说明一下“因此,如果您要初始化此列表一次,那么 volatile 将根本无法帮助您”
  • 当然。听起来您从未更改过变量(即myList = someNewList)。 volatile 的唯一帮助是确保每个线程上的myList 指向正确的东西。因为您没有重新分配变量(我猜),所以它是多余的
  • 关于“初始化”的更多信息:它是 Java 多线程模式的一部分。 volatile 使该字段可见,并且还使所有先前的操作可见(不是迈克尔所说的那样)。但它对后续操作没有任何作用。参考文献Brian Goetz 的 Java Concurrency in Practice 和这个相关问题:stackoverflow.com/questions/801993/…
  • 请发布 List 的特定索引如何更新的代码。例如,如果代码将索引设置为空,然后将其设置为新的布尔值,那么除非到处都有空检查,否则这将不是线程安全的。

标签: java multithreading arraylist


【解决方案1】:

正如它所说的in the Javadoc of ArrayList

请注意,此实现不同步。如果多个线程同时访问一个 ArrayList 实例,并且至少有一个线程在结构上修改了列表,则必须对外同步。

您没有在结构上修改列表,因此您不需要同步。

您想要同步的另一个原因是避免读取过时的值;但你说那不在乎。

因此没有理由进行同步。

编辑update #3

如果您确实关心读取过时的值,则确实需要同步。

避免锁定整个列表的同步替代方法是将其设为List<AtomicBoolean>:这不需要列表同步,因为您不会更改存储在列表中的值;但是读取AtomicBoolean 值可以保证可见性。

【讨论】:

  • @Michael 你不必这样做,你只需调用list.set(i, Boolean.FALSE)。并且“仅仅设置元素的值不是结构修改。”。
  • 很公平。你应该添加那个。如果你这样做,set 会获取索引
  • 原子布尔值是我正在寻找的 :) 感谢您的周到回复!
【解决方案2】:

这取决于当一个元素为真时你想要做什么。考虑一下您的代码,一个单独的线程会弄乱您正在查看的值:

if (!myList.get(index)) {    // <--- at this point, the value is True, so go to else branch
   return;
} else {
  // <--- at this point, other thread sets value to False.

  // do some operations to determine if we should
  // update the value of myList to False.

  // **** Do these operations assume the value is still True?
  // **** If so, then your application is not thread-safe.

  if (needToUpdateList) {
      myList.set(index, False);
   }
}

【讨论】:

  • 这也是我的想法,OP 没有仔细考虑“陈旧值”的真正含义。 “陈旧值”通常意味着所讨论的代码没有定义的语义(它可以做任何事情,并且基本上是随机的)。但是OP说他们不在乎,所以...
【解决方案3】:

更新

我之前说过这些线程不关心过时的值,这是真的。但是,我有另一个线程只读取这些值并执行一个最终操作。该线程确实关心陈旧性。同步要求有变化吗?

你只是让很多非常好的答案无效。

是的,现在同步很重要。事实上,原子性可能也很重要。使用同步列表,甚至可能是并发列表或某种类型的映射。

任何时候你读-修改-写列表,你可能还必须持有同步锁以保持原子性:

synchronized( myList ) {
    if (!myList.get(index)) {
       return;
    } else {
      // do some operations to determine if we should
      // update the value of myList to False.
      if (needToUpdateList) {
          myList.set(index, False);
       }
    }
}

编辑:为了减少持有锁的时间,很大程度上取决于“执行某些操作”需要多长时间,但CocurrentHashMap 以一些额外开销为代价减少了锁争用。可能值得分析您的代码以确定实际开销以及哪种方法更快/更好。

ConcurrentHashMap<Integer,Boolean> myList = new ConcurrentHashMap<>();
//...

if( myList.get( index ) != null ) return;
   // "do some opertaions" here
if( needToUpdate )
   myList.put( index, false );

但我仍然不相信这不是过早的优化。首先编写正确的代码,完全同步列表可能没问题。然后分析工作代码。如果您确实发现了瓶颈,那么您可以担心减少锁争用是否是一个好主意。但可能代码瓶颈不会出现在锁竞争中,实际上会完全不同。

【讨论】:

  • 您好,代码的锁定版本工作正常。在过去的一个月里,我已经做了足够多的分析,以确定在列表上执行更新是瓶颈,因此也是这篇文章。进行更新时,原子性实际上并不重要,只要列表中的 set 操作是原子的。我目前已将问题减少到:1 个线程正在执行更新。另一个线程将在 10 分钟后读取此更新值。有没有办法“发布”更新而无需锁定每个更新?
  • 在字里行间阅读,我想我宁愿看到你使用并发队列。任何需要进一步更新的作业都会被放入队列中,然后一个线程读取队列以确定要进一步处理的作业。但你真的没有给我们太多信息。
  • 您明白,当您编辑评论时我不会收到通知,对吧? @user1848861
  • 嗨@mar​​kspace 感谢您对我的问题的回答,不,我通常不会在stackoverflow 中发布问题,所以通知系统对我来说是新的。 :)
【解决方案4】:

我做了一些谷歌搜索,发现每个线程都可能将值存储在注册表或本地缓存中。规范仅对何时将数据写入共享/全局内存提供一些保证。

https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5

基本上是 volatile、synchronized、thread.start()、thread.join()...

所以是的,使用 AtmoicBoolean 可能是最简单的,但您也可以同步或创建一个包含 volatile 布尔值的类。

查看此链接: http://tutorials.jenkov.com/java-concurrency/volatile.html#variable-visibility-problems

【讨论】:

  • 嘿,是的,我就是这么想的。问题是最终的一致性。最终一致性的时间表取决于什么?
  • 我做了一些谷歌搜索,发现我错了。您基本上必须执行建议的其中一项操作来强制写入内存。
  • 感谢您跟进您的回答。我现在要使用 AtomicBoolean :D
猜你喜欢
  • 2011-02-10
  • 2013-11-12
  • 2019-12-01
  • 2021-01-24
  • 1970-01-01
  • 2010-10-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多