集合与多线程

许多应用程序依赖并发和异步机制。然而,编写这样的代码很容易出错。这就是我们需要并发集合(Concurrent Collections) 的原因。

经典集合与多线程

如你所知,多个线程可能会同时访问相同的数据,如果不使用某种同步机制,通常会导致各种问题。

当多个线程访问一个集合时,也会出现类似问题:

  • 大多数经典集合,如 ArrayListMapSet,都是非同步的,因此不具备线程安全性

  • 有一些集合类型,如 HashtableVectorStack(将在后续主题中讨论),是完全同步的,因此是线程安全的,但它们的性能很低

  • 当一个线程正在遍历一个完全同步的标准集合,而另一个线程试图向其添加新元素时,可能会抛出一个运行时异常 ConcurrentModificationException

下面的程序展示了一个竞态条件(race condition) 的例子,当两个线程向同一个集合中添加元素时就会发生这种情况:

import kotlin.concurrent.thread

fun addNumbers(target: MutableList<Int>) {
    for (i in 0 until 100_000) target += i
}

fun main() {
    val numbers = mutableListOf<Int>()

    val writer = thread(start = false, name = "Thread 1", block = {
        addNumbers(numbers)
    })
    writer.start()

    addNumbers(numbers) // 主线程也添加数字

    writer.join() // 等待子线程完成

    println(numbers.size) // 结果可能会变化
}
解释代码

预期的结果是 200000(主线程和子线程各添加 100000 个元素),但实际运行时,每次结果可能都不同,部分元素会丢失。

我们运行程序三次,得到以下结果:

162527
140487
143736

在多线程环境下,不要在未加同步的情况下使用标准集合。不过,需要注意的是,显式同步也可能导致性能下降,以及在大型程序中出现难以发现的错误。


并发集合(Concurrent Collections)

为了避免上述手动同步带来的问题,Java 类库提供了一些专门为多线程设计的集合实现,这些集合是线程安全的。你可以在 java.util.concurrent 包中找到它们,其中包括:

  • ConcurrentMap

  • ConcurrentLinkedQueue

  • CopyOnWriteArrayList

这些并发集合大大简化了现代 Java(或 Kotlin)应用的开发。

与使用 @Synchronized 注解不同,并发集合使用更复杂的同步原语(如锁分段、原子变量等)和无锁算法(lock-free algorithm) 来保证线程安全,并同时保持较高的性能

注意:如果你的程序并不需要多线程处理,那么还是建议使用经典集合,它们在单线程环境中性能更优。


总结

在 Kotlin 中,如果你需要开发多线程程序,可以使用并发集合。它们已经实现了同步机制,能够解决多线程操作中的同步问题。像 ConcurrentMapConcurrentLinkedQueueCopyOnWriteArrayList 等集合是实现并发程序的良好选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值