使用 Kotlin 进行 Android 编程
by Pierre-Olivier Laurence, Amanda Hinchman-Dominguez, G. Blake Meike, Mike Dunn
第 5 章:螺纹安全 螺纹安全
本作品已使用人工智能进行翻译。欢迎您提供反馈和意见:translation-feedback@oreilly.com
Java 5 引入java.util.concurrent包后,线程开始被普遍用于提高复杂应用程序的性能。在图形(或标题)应用程序中,线程通过减少处理信息以呈现视图(用户可以在屏幕上看到并与之交互的编程组件)的主线程的负载来提高响应速度。当程序中创建了主线程或用户界面线程概念的线程时,就被称为后台线程。这些后台线程通常接收并处理用户交互事件,如手势和文本输入;或其他形式的数据检索,如从服务器读取数据;或本地存储,如数据库或文件系统。在服务器端,使用线程的后端应用程序可以充分利用现代 CPU 的多核功能,从而提高吞吐量。
然而,使用线程也有其自身的风险,本章将为您一一道来。线程安全可以看作是规避这些风险的一系列技术和良好实践。这些技术包括同步、互斥、阻塞 与非阻塞。线程限制等更高层次的概念也很重要。
本章的目的是向你介绍一些重要的线程安全概念,这些概念将在后续章节中使用。不过,我们不会广泛介绍线程安全。例如,我们不会解释对象发布或提供 Java 内存模型的详细信息。这些都是高级主题,我们建议您在理解本章讲解的概念后再学习。
线程问题示例
为了理解什么是线程安全,我们将选取一个简单的例子来说明线程安全问题。当程序同时运行多个线程时,每个线程都有可能与其他正在运行的线程同时进行操作。但这并不意味着一定会发生。当这种情况发生时,你需要防止一个线程访问正在被另一个线程变异的对象,因为它可能会读取对象的不一致状态。同时突变也是如此。确保每次只有一个线程可以访问代码块,这就是所谓的互斥。以下面的代码为例
classA{varaList:MutableList<Int>=ArrayList()privatesetfunadd(){vallast=aList.last()// equivalent of aList[aList.size - 1]aList.add(last+1)}init{aList.add(1)}}
add() 方法取列表的最后一个元素,加 1,并将结果追加到列表中。如果两个线程试图同时执行add() ,预期会出现什么行为?
当第一个线程引用最后一个元素时,另一个线程可能已经来得及执行整个aList.add(last + 1) 行。1在这种情况下,第一个线程读取最后一个元素为 2,并将 3 追加到列表中。结果列表将是[1, 2, 3] 。另一种情况也有可能发生。如果第二个线程没有时间追加新值,那么两个线程将读取最后一个元素的相同值。假定其他执行过程都顺利进行,我们得到的结果就是[1, 2, 2] 。还有一个危险可能发生:如果两个线程试图在同一时间向列表追加新元素,就会抛出一个ArrayIndexOutOfBoundsException将被抛出。
根据线程的交错情况,结果可能会有所不同。我们根本无法保证一定会得到结果。这些都是不具备线程安全特性的类或函数的表现,它们在被多个线程访问时可能无法正常运行。
那么,我们该如何解决这种潜在的不当行为呢?我们有三个选择:
-
不要跨线程共享状态。
-
跨线程共享不可变状态
-
更改我们的实现,以便多个线程可以使用我们的类,并获得可预测的结果。
有多种策略可以实现某种线程安全,每种策略都有自己的优势和注意事项,因此开发人员必须能够评估自己的选择,并选择最适合线程问题需求的策略。
第一种选择相对明显。当线程可以在完全独立的数据集上工作时,就不会有访问相同内存地址的风险。 ...