Java ArrayList的设置线程安全性

时间:2018-10-29 15:57:18

标签: java multithreading arraylist

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

线程只能进行的修改是将ArrayList的元素从True设置为False。

所有线程也将同时读取ArrayList,但是可以读取陈旧的值。

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

问题:

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

这是有关线程如何使用此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);
   }
}

更新

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

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

4 个答案:

答案 0 :(得分:2)

in the Javadoc of ArrayList所示:

  

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

您并不是在结构上修改列表,因此您不需要进行同步。

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

因此,没有理由进行同步。

编辑update #3

如果您确实想读取过时的值,则需要进行同步。

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

答案 1 :(得分:0)

这取决于元素为true时要执行的操作。考虑一下您的代码,其中一个单独的线程弄乱了您正在查看的值:

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

答案 2 :(得分:0)

  

更新

     

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

您只是使很多非常好的答案无效。

是的,同步现在很重要。实际上,原子性可能也很重要。使用同步列表,甚至可以使用并发列表或某种形式的地图。

每当您对列表进行读-修改-写操作时,您可能还必须持有同步锁以保留原子性:

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

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

答案 3 :(得分:0)

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

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

基本上是易失的,同步的,thread.start(),thread.join()...

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

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

相关问题