多线程Guide

什么是多线程?

多线程意味着您在同一应用程序中具有多个执行线程。线程就像执行应用程序的独立CPU。因此,多线程应用程序就像具有多个CPU同时执行代码的不同部分的应用程序。

在其中执行两个线程的应用程序。

线程不等于CPU。通常,单个CPU将在多个线程之间共享其执行时间,并在给定的时间量内在每个线程之间执行切换。也可以使应用程序的线程由不同的CPU执行。

具有线程由不同线程执行的多个应用程序。

为什么要多线程?

为什么要在应用程序中使用多线程有多个原因。多线程的一些最常见原因是:

  • 更好地利用单个CPU。
  • 更好地利用多个CPU或CPU内核。
  • 更好的用户响应性体验。
  • 更好的用户公平性体验。

我将在以下各节中详细解释每个原因。

更好地利用单个CPU

最常见的原因之一是能够更好地利用计算机中的资源。例如,如果一个线程正在等待对通过网络发送的请求的响应,则另一线程可以同时使用CPU来执行其他操作。此外,如果计算机具有多个CPU,或者CPU具有多个执行核心,则多线程还可以帮助您的应用程序利用这些额外的CPU核心。

更好地利用多个CPU或CPU内核

如果计算机包含多个CPU或CPU包含多个执行核心,则您需要为应用程序使用多个线程才能使用所有CPU或CPU核心。单个线程最多只能使用一个CPU,如上所述,有时甚至不能完全利用单个CPU。

更好的用户响应性体验

使用多线程的另一个原因是为了提供更好的用户体验。例如,如果您单击GUI中的按钮,并导致通过网络发送请求,那么哪个线程执行此请求就很重要。如果您使用同一个线程去更新GUI,则在GUI线程等待请求响应时,用户可能会遇到GUI“挂起”的情况。取而代之,建议的做法为把这样的请求(发起网络请求)通过后台线程执行,因此GUI的那个线程可以自由地同时响应用户的请求。

更好的用户公平性体验

第四个原因是在用户之间更公平地共享计算机资源。例如,假设一台服务器接收来自客户端的请求,并且只有一个线程来执行这些请求。如果客户端发送的请求需要很长时间才能处理,则所有其他客户端的请求都必须等待,直到一个请求完成。通过使每个客户端的请求都由其自己的线程执行,则没有一个任务可以完全垄断CPU。

多线程与多任务

过去,一台计算机只有一个CPU,并且一次只能执行一个程序。大多数小型计算机的功能实际上不足以同时执行多个程序,因此这些小型机也没有尝试过。公平地讲,许多大型机系统拥有能够一次执行多个程序的能力比个人计算机早很多年。

多任务

后来出现了多任务处理,这意味着计算机可以同时执行多个程序(AKA任务或进程)。但是,这并不是真正的“同时”。单个CPU在程序之间共享。操作系统将在运行的程序之间进行切换,并在切换之前执行每个程序一会儿。

随着多任务处理,软件开发人员面临着新的挑战。程序不再假定拥有所有可用的CPU时间,也不拥有所有的内存或任何其他计算机资源。一个“好公民”程序应释放不再使用的所有资源,以便其他程序可以使用它们。

多线程

后来又出现了多线程,这意味着您可以在同一程序中拥有多个执行线程。可以将一个执行线程视为一个CPU执行的程序。当有多个线程执行同一程序时,就像在同一程序里有多个CPU执行它。

多线程很难

多线程是提高某些类型程序性能的好方法。但是,多线程处理比多任务处理更具挑战性。这些线程在同一程序中执行,因此同时在读取和写入相同的内存。这可能会导致在单线程程序中看不到的错误。在单个CPU机器上可能看不到其中一些错误,因为两个线程从未真正“同时”执行。但是,现代计算机配备了多核CPU,甚至还配备了多个CPU。这意味着可以由单独的内核或CPU同时执行单独的线程。

多CPU计算机上的多线程

如果一个线程读取了另一个线程正在写入的内存位置,那么读线程最终将读取什么值?旧值?写线程写的值?还是两者之间混合的值?或者,如果两个线程同时写入相同的内存位置,完成后将剩下什么值?由第一个线程写的值?第二个线程写的值?还是两个值的混合编写?

没有适当的预防措施,任何这些结果都是可能的。该行为甚至是不可预测的。结果可能会不时改变。因此,作为开发人员,重要的是要知道如何采取正确的预防措施-意味着学习控制线程如何访问共享资源(如内存,文件,数据库等)。这是本Java并发性教程解决的主题之一。

Java中的多线程和并发

Java是最早使开发人员可以使用多线程的语言之一。Java从一开始就具有多线程功能。因此,Java开发人员经常面临上述问题。这就是我在Java并发上编写此捷径的原因。谨此提醒自己,以及可能从中受益的其他Java开发人员。

本教程主要关注Java中的多线程,但有些在多线程中出现的问题会和多任务以及分布式系统中出现的存在类似,因此该系列会将多任务和分布式系统方面作为参考,所以叫法上称为“并发性”,而不是“多线程”。

并发模型

第一个Java 并发模型假定在同一应用程序中执行的多个线程也将共享对象。这种类型的并发模型通常称为“共享状态并发模型”。许多并发语言构造和实用程序旨在支持这种并发模型。

但是,自从编写第一本Java并发书籍以来,甚至自Java 5并发实用工具发布以来,并发体系结构和设计领域已经发生了很多事情。

共享状态并发模型导致许多并发问题,这些问题很难优雅地解决。因此,被称为“无共享”或“分离状态”的替代并发模型已经普及。在单独或者分离状态得并发模型中,线程不共享任何对象或数据。这避免了共享状态下并发模型的许多并发访问问题。

出现了新的异步“分离状态”的平台和工具包,例如Netty,Vert.x和Play / Akka和Qbit(就听过netty囧)。新的非阻塞并发算法已经发布,并且新的非阻塞工具(例如LMax Disrupter)已添加到我们的工具箱中。Java 7中的Fork and Join框架和Java 8中的collection stream API引入了新的函数式编程并行性。

通过所有这些新开发,现在是时候更新本Java Concurrency教程了。因此,本教程再次进行中。只要有时间编写新教程,它们就会发布。

Java并发学习指南

如果您不熟悉Java并发,建议您遵循以下学习计划。您也可以在此页面左侧的菜单中找到所有主题的链接。

通用并发和多线程理论:

Java并发基础知识:

Java并发性的典型问题:

Java并发构造可帮助解决上述问题:

Java并发实用程序(java.util.concurrent):

进一步的主题: