● Java并发教程视频
● 什么是多线程?
● 为什么要使用多线程?
○ 更好的利用单个CPU
○ 更好的利用多个CPU或CPU内核
○ 更好的关于响应性的用户体验
○ 更好的关于资源公平利用的用户体验
● 多线程与多任务
○ 多任务
○ 多线程
● 多线程很难
● Java中的多线程和并发
● 并发模型
● Java并发学习指南
Java并发是一个涵盖Java平台上的多线程,并发和并行的术语。 其中包括Java并发工具,问题和解决方案。 本Java并发教程涵盖了多线程的核心概念,并发构造(concurrency constructs),并发问题,成本,以及与Java多线程相关的好处。
Java并发教程视频
如果你喜欢视频,我有一个视频播放列表,其中涵盖了与本教程系列相同的主题。 你可以在此处找到视频播放列表:
Java并发和多线程-视频播放列表(原链接到油管,如果大家有需要可留言)
什么是多线程?
多线程意味着你在同一个应用程序中有多个执行线程。 一个线程就像是一个独立的CPU在执行你的应用程序。 因此,一个多线程应用程序,就像是同时在多个CPU上执行着同一个应用程序的不同部分的代码。
但是,线程不等同于CPU。 通常,单个CPU会在多个线程之间共享其执行时间,并在给定的时间片内,轮换着执行每个线程。另外,应用程序的多个线程也可以在不同的CPU上执行。
为什么要使用多线程?
为什么要在应用程序中使用多线程有多个原因。 使用多线程的一些最常见原因是:
● 更好的利用单个CPU。
● 更好的利用多个CPU或CPU内核。
● 更好的关于响应性的用户体验。
● 更好的关于资源公平利用的用户体验。
我将在下面各节中详细解释每个原因。
更好的利用单个CPU
最常见的原因之一是能够更好地利用计算机中的资源。 例如,如果一个线程正在等待一个通过网络发送的请求的响应,则另一线程可以同时使用CPU来执行其他操作。 此外,如果计算机具有多个CPU,或者该CPU具有多个执行核心,则多线程还可以帮助你的应用程序利用这些额外的CPU核心。
更好的利用多个CPU或CPU内核
如果计算机包含多个CPU或CPU包含多个执行核心,那么,你需要在应用程序中使用多线程,这样才能使用到所有CPU或CPU核心。 一个线程最多只能使用一个CPU,并且如上所述的原因,有时甚至不能完全利用一个CPU。
更好的关于响应性的用户体验
使用多线程的另一个原因是,为了提供更好的用户体验。 例如,如果你单击用户界面中的按钮,并触发通过网络发送请求,那么哪个线程执行此请求就很重要了。 如果使用的线程也正在更新用户界面,则在用户界面线程等待请求响应时,用户可能会遇到用户界面“挂起”的情况(表现为用户界面无法操作的现象)。为了解决这个问题,一个替代方案是,这样的请求可以由后台线程执行,因此用户界面线程,就可以自由地同时响应其他用户请求了。
更好的关于资源公平利用的用户体验
第四个原因是,在用户之间更公平地共享计算机资源。 例如,假设一台服务器接收来自客户端的请求,并且只有一个线程来执行这些请求。 如果客户端发送的请求要花很长时间才能处理完成,则所有其他客户端的请求都必须等待,直到该请求完成为止。 通过使每个客户端的请求都由其自己的线程执行,则没有一个任务可以完全垄断CPU。
多线程与多任务
过去,一台计算机只有一个CPU,并且一次只能执行一个程序。 大多数小型机的运算能力,实际上不足以支持同时执行多个程序,因此没有尝试过。 公平地说,在个人计算机能够同时执行多应用程序,很多年前许多大型机系统就已经支持了。
多任务
后来出现了多任务处理,这意味着计算机可以同时执行多个程序(AKA任务或进程)。 但是,这并不是真正的“同时”。 单个CPU在程序之间共享。 操作系统在,正在运行的程序之间进行切换,并在切换之前执行每个程序一会儿。
伴随着多任务处理,软件开发人员面临着新的挑战。 程序不能再假定,它具有所有可用的CPU时间,也不能拥有,所有的内存或任何其他计算机资源。 一个“好公民”程序,应释放不再使用的所有资源,以便其他程序可以使用它们。
多线程
再后来,又出现了多线程,这意味着你可以在同一程序中拥有多个执行线程。可以将执行线程视为执行程序的CPU。 当你有多个线程执行同一程序时,就像是多个CPU在同一个应用程序中执行。
多线程很难
多线程是提高某些类型程序性能的好办法。 但是,多线程处理比多任务处理更具挑战性。 这些线程在同一个程序中执行,因此正在同时读取和写入相同的内存。 这可能会导致在单线程程序中看不到的错误。 在单个CPU机器上可能看不到其中一些错误,因为两个线程从未真正“同时”执行。 但是,现代计算机配备了多核CPU,甚至也配备了多个CPU。 这意味着可以由单独的内核或CPU同时执行单独的线程。
如果一个线程在另一个线程写入内存位置时读取了一个内存位置,那么第一个线程最终将读取什么值? 旧值? 第二个线程写的值? 还是两者之间混合的值? 或者,如果两个线程正在同时写入同一内存位置,那么完成后将剩下什么值? 由第一个线程写的值? 第二个线程写的值? 还是两个值的混合编写?
没有适当的预防措施,任何这些结果都是可能的。 这种行为甚至是无法预料的。 结果可能会不时发生变化。 因此,作为开发人员,重要的是要知道如何采取正确的预防措施,这意味着学习控制线程如何访问共享资源(如内存,文件,数据库等)。这是本Java并发教程解决的主题之一。
Java中的多线程和并发
Java是最早使开发人员可以使用多线程的语言之一。 Java从一开始就具有多线程功能。 因此,Java开发人员经常面临上述问题。 这就是我在Java并发中提到“多线程”这一线索(trail)的原因。 谨此提醒自己,以及可能从中受益的其他Java开发人员。
本教程主要关注Java中的多线程,但是多线程中发生的某些问题类似于多任务和分布式系统中发生的问题。 因此,对多任务和分布式系统的引用也可能出现在“多线程”这一线索(trail)中。 因此,我使用“并发”而不是“多线程”。
并发模型
第一个Java并发模型,假定在同一应用程序中执行的多个线程也将共享对象。 这种类型的并发模型通常称为“共享状态并发模型(shared state concurrency model)”。 许多并发语言结构(language constructs)和实用程序(utilities)旨在支持此并发模型。
但是,自从编写第一本Java并发书籍以来,甚至自Java 5并发实用工具发布以来,并发体系结构和设计领域已经发生了很多变化。
共享状态并发模型导致许多并发问题,这些问题很难优雅地解决。 因此,被称为“无共享”或“分离状态”的替代并发模型已经普及。 在隔离状态并发模型(separate state concurrency model)中,线程不共享任何对象或数据。 这避免了共享状态并发模型的许多并发访问问题。
出现了新的异步“隔离状态(separate state)”平台和工具包,例如,Netty、Vert.x、Play / Akka和Qbit。 新的非阻塞并发算法已经发布,并且新的非阻塞工具(例如LMax Disrupter)已添加到我们的工具箱中。 Java 7中的Fork and Join框架和Java 8中的collection stream API引入了新的函数式编程并行性。
通过所有这些新开发模式,现在是时候更新本Java Concurrency教程了。 因此,本教程再次进行中。 只要有时间编写新教程,它们就会发布。
Java并发学习指南
如果你不熟悉Java并发,建议你遵循以下学习计划。 你也可以在此页面左侧的菜单中找到所有主题的链接。
通用并发和多线程理论:
● 多线程的好处
● 多线程成本
● 并发模型
● 同线程(Same-threading)
● 并发与并行
Java并发基础知识:
● 创建和启动Java线程
● 比赛条件和关键部分
● 线程安全和共享资源
● 线程安全性和不变性
● Java内存模型
● Java同步块
● Java易失性关键字
● Java线程局部变量(ThreadLocal)
● Java线程信令
Java并发性的典型问题:
死锁
● 防止死锁
● 饥饿与公平(Starvation and Fairness)
● 嵌套管程锁死(Nested Monitor Lockout)
● 滑动条件(Slipped Conditions)
Java并发结构有助于解决上述问题:
● Java中的锁
● Java中的读/写锁
● 重入锁死(Reentrance Lockout)
● 信号量
● 阻塞队列
● 线程池
● 比较和交换
Java并发实用程序(java.util.concurrent):
● Java并发实用程序-java.util.concurrent
进一步的主题:
● 剖析同步器
● 非阻塞算法
● 阿姆达尔定律(Amdahl's Law)
● 参考
译自:Java Concurrency and Multithreading Tutorial
Jakob Jenkov
Last update: 2020-06-08