Scala:如何测试 mutable.Set 的并发性

Scala: How to test the concurrency of a mutable.Set

在 Scala 中,并发和非并发 Set 的类型完全相同:

import collection.JavaConverters._

// A regular Set in Scala, not concurrent.
val regularSet: mutable.Set[Int] = mutable.Set[Int]()

// A concurrent set. It has the same type as a regular set, but underneath, it is actually concurrent. In my opinion, this is a flaw in the type system for Scala collections
val concurrentSet: mutable.Set[Int] = java.util.concurrent.ConcurrentHashMap.newKeySet[Int]().asScala

我想要一种实际测试集合是否并发的方法。

回答

您可以通过创建大量试图从共享 mutable.Set

add/remove 相同元素的线程来凭经验对其进行测试
import java.util.concurrent.{Callable, ExecutorService, Executors}
import scala.collection.mutable

def testSet(set: mutable.Set[Int]) {
  val e: ExecutorService = Executors.newFixedThreadPool(5)
  val tasks = for (i <- 0 to 50000) yield {
    e.submit(new Callable[Unit]() {
      override def call() {
        for (j <- 0 to 10) {
          set.add(i + j)

          // This will throw a Null Pointer Exception for the non-concurrent version
          // This is because it is trying to remove something that another thread already removed.
          set.remove(i + j) 
        }
      }
    })
  }
  for (result <- tasks) result.get()
  e.shutdown()
}

// Can't check the type! They have the same type.
val regularSet: mutable.Set[Int] = mutable.Set[Int]()
val concurrentSet: mutable.Set[Int] = java.util.concurrent.ConcurrentHashMap.newKeySet[Int]().asScala

testSet(concurrentSet) // Success!
testSet(regularSet) // FAILS! Almost always throws a NullPointerException on my system.

限制

运行 测试将需要系统资源,例如线程和 CPU 时间。在 运行 时间 运行 在生产中使用它并不合适。

这不是演绎证明。由于竞争条件是随机的,因此测试 class 将 non-concurrent 对象确认为并发的可能性很小。但是,运行延长测试时间会导致检测到 non-concurrent 对象的概率接近确定性。

附加评论

理想情况下,会有一种方法可以使用反射和类型系统来查看底层对象是什么,并测试它是否是 ConcurrentHashMap(我认为 Scala 的主要缺陷,因为某些函数运行 一个 multi-threaded 任务不能有效地阻止函数调用者传入一个 non-concurrent 对象)。

但这至少提供了一种测试它的经验方法。

致谢

How can I test that ConcurrentHashMap is truly thread-safe? 中提出了类似的问题。我修改了它以使用 Sets:

推荐

我建议使用 concurrent.Map[T, Unit] 而不是 mutable.Set[T]。原因是您将能够利用类型系统以 100% 的信心确保您的 function/class 操作的对象实际上是并发的。

是的,你会失去Set语义,比如.add(setMember)方法。但你会获得安全感。

如果您坚持使用并发 mutable.Set,请考虑制作一个包装器 class 容器,以防止意外初始化为 non-concurrent mutable.Set