Java 并发和多线程系列

334 阅读8分钟

译序

这个系列来自于对 tutorials.jenkov.com/java-concur… 的翻译,该系列的最大特点是简洁而又全面。说它简洁是因为对每个知识点以最简明的文字来描述,没有任何的故弄玄虚,个人认为把一个复杂的事情用简洁的文字表达出来,是一种高超的能力,说明你真的掌握了它。这个系列之前看过一遍,但是有些地方不是很理解,所以也借此机会把一些感悟和疑惑记录下来。Ok,let's get started

译文

Java并发这个术语覆盖了java平台上的多线程,并发和并行。它包含了java并发工具,问题集以及解决方案集。该系列覆盖了多线程的核心概念,并发构造,并发问题,以及多线程的利弊。

什么是多线程

多线程是指在同一个程序内部有多个线程运行,一个线程就像一个独立的CPU运行在你的程序内,因此,多线程应用就像在同一时刻多个CPU执行代码的不同部分。

译者注:注意上面加粗的文字,这里用的是“就像”,而不是“就是”,因为在单核机器上,是通过线程切换的方式让用户觉得好像多个任务在并行运行,而在多核机器上才是真正的并行运行。

然而一个线程并不是完全等价于一个CPU。通常一个CPU可以在多个线程中共享执行时间,即通过切换让每个线程执行特定时间。也可以让一个程序内部的线程运行到不同的CPU上。

为什么使用多线程

在程序中使用多线程的原因很多,最常见的如下:

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

在接下来的章节总我将详细介绍以上原因

更好的利用单CPU

最常见的原因之一就是更好的利用计算机的资源。例如,如果一个线程在等待一个网络响应,那么另一个线程可以在同时做一些其他事情。除此之外,如果一个机器拥有多核或多CPU,那么多线程可以让你的程序使用这些额外资源

更好的利用多核或多CPU

如果一台机器拥有多个CPU或者一个CPU拥有多核,那么你需要使用多线程来使用这些多CPU和多核。一个线程最多使用一个CPU,就像我上面说的,即使一个CPU也不能保证被充分利用。

更好的用户响应性体验

使用多线程的另外一个原因是提高用户体验。例如:如果你点击界面上的一个按钮,该操作通过网络发送一个请求,接下来哪个线程处理这个请求将变得很重要。如果用同一个线程来更新界面,那么在该线程等待响应的同时,用户很可能会体验到卡顿。相反,如果使用一个后台线程处理这个请求,那么界面会愉快的接受其他的用户请求

更好的公平性体验

第四个原因是多个用户更公平的分享计算机资源。假设一台server接受客户端的请求,并且只有一个线程处理这些请求,如果对一个请求的处理占用了太长时间,那么其他请求将不得不等待这个请求处理完。让每个请求在单独的线程中执行,这样就不会让单个线程完全独占CPU了。

多线程和多任务

回到单处理器时代,某一时刻只能运行一个程序。因此大多数的小型计算机没有足够的能力在同一时刻处理多个程序,这是迫不得已。相比个人电脑,只有大型主机可以运行多个程序来保证公平性

多任务

随后出现的多任务标志着计算机在某一个时刻可以运行多个任务(AKA任务或进程),但是它不是真正的同时。单个CPU被多个进程共享,每个进程被执行一会然后切换

伴随和多任务,开发人员也面临新的挑战。程序不可以独占整个CPU的可用时间,内存和其他资源也如此。一个“良民”应当释放它所不用的资源给其他程序使用。

多线程

随后出现的多线程意味着在同一个程序内部有多个线程执行。单线程执行可以看作一个CPU在执行程序,当一个程序内有多个线程执行,就像多个CPU在执行一个程序。

多线程很难

对某些类型的程序,多线程极大的提高了性能。但是相比于多任务,多线程更具挑战性。线程在同一个程序内运行,因此可以同时对相同内存进行读写,这导致了在单线程系统中不会出现的错误。因为单线程系统不会出现真正的“同时”。然而,现在计算机都是多CPU,甚至一个CPU包含多核,这就意味着单独的线程可以在单独CPU或核中同时运行。

如果一个线程在读,同时另一个线程在写同一块内存,那么第一个线程最后会读到什么?老值?还是第二个线程写的值,还是两个的混合值?或者,如果两个线程同时对一块内存进行写操作,那么最后的值是什么?第一个线程写的值?第二个线程写的值?还是两个的混合值?

没有适当的保护,任何情况都有可能。结果甚至都无法预测。结果可能随时改变。因此对于开发人员来说,采取适当的保护--学会控制线程如何访问这些共享资源如内存,文件数据库等,将变得非常重要。这也是该系列想要解决主题之一。

Java中的多线程和并发

Java是第一批面向开发人员,友好支持多线程语言之一。它很早就具备多线程的能力,因此,java开发人员经常面对上述问题。这也是我编写这个系列的原因。除了作为我的笔记,任何java开发人员可以从中受益。

虽然这个系列主要集中在java多线程,但是一些在多线程中出现的问题跟在多任务和分布式系统中的类似,因此在多任务或分布式系统中的参考也会出现在本系列中。因此下文中使用并发而非多线程

并发模型

第一个并发模型假定在一个程序内部有多个线程执行,并且它们共享对象。这类并发模型一般被称作“共享状态并发模型”。很多并发语言的构造和使用在设计时就支持这一模型。

然而,从第一批并发书籍,甚至java 5并发包刚发布之时,在并发架构和设计领域就大量的出现了这一模型。该模型导致大量问题无法被很优雅地解决。所以,一种替代的被称作“无共享”或“独立状态”的模型逐渐流行起来。在独立状态的并发模型中,县城不共享任何状态或数据,这就避免了在共享状态模型中出现的并发访问问题。

像Netty,Vert.x,Play/Akka以及Qbit这些新的,异步的独立状态平台和工具集已经出现。一些新的并发算法也已经公布,除此之外,像LMax干扰器已经加入到我们的工具集中。新的函数式可编程并行度(functional programming parallelism)已经加入到java 7 中,此外集合的流式API也加入到了java 8中。

伴随着这些新的开发,是时候更新这个java并发系列了。因此,这个系列再次变为更新中状态,一旦有时间我将发布这个新的系列。

Java并发编程学习指导

如果你对并发编程不熟悉,那么我建议你按照以下计划学习。当然你也可以从左侧的菜单栏中链接到这些主题

译者注: 因为作者的网站有个专门的左侧导航菜单,而掘金没有,所以请读者按照下面的链接进行学习,链接都是一样的。(等翻译完再更新以下链接)

通用的并发和多线程理论

Java并发基础

  • 创建和启动线程
  • 竟态条件和临界区
  • 线程安全和共享资源
  • Java内存模型
  • Java同步块
  • Java Volatile关键字
  • Java ThreadLocal
  • Java 线程信号量

Java并发中的经典问题

  • 死锁
  • 防止死锁
  • 饥饿和公平
  • Java监视器锁定
  • Slipped 条件(等弄明白这个主题,再选择一个合适的名词翻译)

应对上述问题的Java并发构造

  • Java中的锁
  • Java中的读写锁
  • Reentrance Lockout(重入锁)
  • Semaphore(信号量)
  • Blocking Queue(阻塞队列)
  • 线程池
  • Compare and Swap(比较和交换)

扩展主题

  • 同步器剖析
  • 非阻塞算法
  • Amdahl 法则
  • 参考

下一篇:多线程优势