阻塞每个方法的同步集合
Synchronized collection that blocks on every method
我有一个在不同线程之间常用的集合。在一个线程中,我需要添加项目、删除项目、检索项目并遍历项目列表。我正在寻找的是一个集合,只要这些方法中的任何一个已经被调用,它就会阻止访问其任何 read/write/remove 方法。因此,如果一个线程检索了一个项目,另一个线程必须等到读取完成才能从集合中删除一个项目。
Kotlin 似乎没有提供此功能。但是,我可以创建一个包装器 class 来提供我正在寻找的同步。 Java 似乎提供了 synchronizedList class 但据我所读,这实际上是为了阻止对单个方法的调用,这意味着没有两个线程可以同时删除一个项目,但一个可以删除而另一个阅读一个项目(这是我试图避免的)。
还有其他解决方案吗?
包装器,例如 synchronizedList
返回的包装器
使用包装器本身作为锁同步对每个方法的调用。因此,一个线程将被阻止调用 get()
,例如,而另一个线程当前正在调用 put()
。 (这似乎是问题的要求。)
但是,正如该方法的文档指出的那样,这对保护 调用序列 没有任何作用,例如您在遍历集合时可能会使用的调用。如果另一个线程在您对 next()
的调用之间更改了集合,那么任何事情都可能发生。 (这就是我认为问题的真正含义!)
为了安全处理,您的选择包括:
- 手动同步。在集合上同步的
synchronized
块中包围对集合的每个调用序列,例如:
val list = Collections.synchronizedList(mutableListOf<String>())
// …
synchronized (list) {
for (i in list) {
// …
}
}
这很简单,如果集合在您的控制之下,也相对容易做到。但是,如果您错过了 any 个序列,那么您可能会遇到意想不到的行为。此外,您需要保持序列简短,以避免长时间持有锁并影响性能。
- 使用并发集合实现,它提供原语,让您在一次调用中完成所需的所有处理,避免迭代和其他序列。
对于地图,Java 的 ConcurrentMap
interface, and high-performance implementations such as ConcurrentHashMap
提供了很好的支持。这些方法允许您在一次调用中迭代、更新单个或多个映射、搜索、减少和许多其他 whole-map 操作,避免任何并发问题。
对于集合(根据 this question) you can use a ConcurrentSkipListSet
, or you can create one from a ConcurrentHashMap
with newKeySet()
。
对于列表(根据 this question), there are fewer options. (I think concurrent lists are much less commonly needed.) If you don't need random access, ConcurrentLinkedQueue
may suffice. Or if modification is much less common than iteration, CopyOnWriteArrayList
可以工作。
java.util.concurrent
包中还有许多其他并发 类,因此值得仔细研究一下,看看其中是否更适合您的特定情况。
- 如果您有特殊要求,您可以编写自己的支持它们的集合实现。显然这是更多的工作,只有当上述方法中的 none 满足您的要求时才值得。
总的来说,我认为值得退一步看看是否真的需要迭代。从历史上看,在从 FORTRAN 到 BASIC 和 C 一直到 Java 的命令式语言中,for
循环一直是传统的选择工具(有时 仅 结构)用于对数据集合进行操作——对于我们这些在这些语言中长大的人来说,这是我们本能地追求的。但是函数式编程范式提供了替代工具,因此在像 Kotlin 这样提供其中一些工具的语言中,最好停下来问问自己“我最终要在这里实现什么?” (通常我们想要的实际上是更新所有条目,或者映射到一个新结构,或者搜索一个元素,或者找到最大值——所有这些在 Kotlin 中都有比 low-level 迭代更好的方法。)
毕竟,如果你能告诉编译器你想做什么,而不是如何去做,那么你的程序可能会更短且更易于阅读和维护,让您有更多时间思考更重要的事情!
我有一个在不同线程之间常用的集合。在一个线程中,我需要添加项目、删除项目、检索项目并遍历项目列表。我正在寻找的是一个集合,只要这些方法中的任何一个已经被调用,它就会阻止访问其任何 read/write/remove 方法。因此,如果一个线程检索了一个项目,另一个线程必须等到读取完成才能从集合中删除一个项目。
Kotlin 似乎没有提供此功能。但是,我可以创建一个包装器 class 来提供我正在寻找的同步。 Java 似乎提供了 synchronizedList class 但据我所读,这实际上是为了阻止对单个方法的调用,这意味着没有两个线程可以同时删除一个项目,但一个可以删除而另一个阅读一个项目(这是我试图避免的)。
还有其他解决方案吗?
包装器,例如 synchronizedList
返回的包装器
使用包装器本身作为锁同步对每个方法的调用。因此,一个线程将被阻止调用 get()
,例如,而另一个线程当前正在调用 put()
。 (这似乎是问题的要求。)
但是,正如该方法的文档指出的那样,这对保护 调用序列 没有任何作用,例如您在遍历集合时可能会使用的调用。如果另一个线程在您对 next()
的调用之间更改了集合,那么任何事情都可能发生。 (这就是我认为问题的真正含义!)
为了安全处理,您的选择包括:
- 手动同步。在集合上同步的
synchronized
块中包围对集合的每个调用序列,例如:
val list = Collections.synchronizedList(mutableListOf<String>())
// …
synchronized (list) {
for (i in list) {
// …
}
}
这很简单,如果集合在您的控制之下,也相对容易做到。但是,如果您错过了 any 个序列,那么您可能会遇到意想不到的行为。此外,您需要保持序列简短,以避免长时间持有锁并影响性能。
- 使用并发集合实现,它提供原语,让您在一次调用中完成所需的所有处理,避免迭代和其他序列。
对于地图,Java 的 ConcurrentMap
interface, and high-performance implementations such as ConcurrentHashMap
提供了很好的支持。这些方法允许您在一次调用中迭代、更新单个或多个映射、搜索、减少和许多其他 whole-map 操作,避免任何并发问题。
对于集合(根据 this question) you can use a ConcurrentSkipListSet
, or you can create one from a ConcurrentHashMap
with newKeySet()
。
对于列表(根据 this question), there are fewer options. (I think concurrent lists are much less commonly needed.) If you don't need random access, ConcurrentLinkedQueue
may suffice. Or if modification is much less common than iteration, CopyOnWriteArrayList
可以工作。
java.util.concurrent
包中还有许多其他并发 类,因此值得仔细研究一下,看看其中是否更适合您的特定情况。
- 如果您有特殊要求,您可以编写自己的支持它们的集合实现。显然这是更多的工作,只有当上述方法中的 none 满足您的要求时才值得。
总的来说,我认为值得退一步看看是否真的需要迭代。从历史上看,在从 FORTRAN 到 BASIC 和 C 一直到 Java 的命令式语言中,for
循环一直是传统的选择工具(有时 仅 结构)用于对数据集合进行操作——对于我们这些在这些语言中长大的人来说,这是我们本能地追求的。但是函数式编程范式提供了替代工具,因此在像 Kotlin 这样提供其中一些工具的语言中,最好停下来问问自己“我最终要在这里实现什么?” (通常我们想要的实际上是更新所有条目,或者映射到一个新结构,或者搜索一个元素,或者找到最大值——所有这些在 Kotlin 中都有比 low-level 迭代更好的方法。)
毕竟,如果你能告诉编译器你想做什么,而不是如何去做,那么你的程序可能会更短且更易于阅读和维护,让您有更多时间思考更重要的事情!