本书简介

简介一般是一本书的一个概括,本书的核心是 Java 并发 ,那么简介中的内容肯定是与并发相关的知识。

读书之前,如果你对并发一无所知,那么一定要带着问题来读书,首先,最直接的问题:

  • 什么是并发?
  • 并发是怎么来的?
  • 并发带来了什么样的问题?
  • 不同的问题是由于什么底层的原因导致的?

刨根问底的弄明白这几个问题,基本上也就对于 并发 有了一个基本而全面的认识。

这里就以问题为线索,以解决问题为目的,来读这一章。

1. 什么是并发?

首先:计算机最早是没有 操作系统 的,每个计算机从头到尾只执行一个程序,这个程序获取所有的资源。
这个年代估计已经很早了,但是想想都可以知道这样是很低效的,并且当时的计算机属于很昂贵的机器,这就更加降低了使用效率。

于是操作系统出现了,有了操作系统之后计算机可以运行多个应用程序了,并且操作系统充当了调度分配者的身份,负责分配计算机资源。

每个程序都是一个独立的进程,每个进程有自己的数据内存空间。于是计算机从单应用时代进入了操作系统出现后的多进程时代。

在这里,本书的简介给出了 操作系统出现的原因:

  • 提升公平性: 也就是多个应用程序使用时间片的方式进行切换,而不是没有操作系统时从头到尾运行一个应用程序
  • 提升资源利用率: 当程序需要等待外部操作(比如网络或者磁盘I/O)时,可以切换到另一个程序继续运行,就提升了计算机的利用率
  • 便利性: 编写多个程序进行计算,在必要的时互相通信,比编写一个巨大的程序来计算所有任务要简单。(有点分布式与巨型单体应用的感觉)

所以为了 性能利用率 , 操作系统出现了。 但是进程还是有点重,因为一个应用程序可能需要很多资源,所以这两个指标继续促进对 进程 进行细分,于是在进程中我们可以同时进行多个控制流,线程 出现了。
比如下面示意图,一个应用程序中有许多个流程并行执行,你在收发群消息的时候,也可以收发好友消息,有时候还会后台给你推送你所在地的新闻,这背后就是多线程 技术的应用。

线程 是轻量级的进程,在「现代操作系统」 中,以线程为基本调度单位,如果没有使用明确的协同机制(比如 Java 中的锁)那么线程将彼此独立执行,所以在多线程环境下会出现许多单线程中没有的问题。

【下面是一个简单的帮助记忆的流程图:】

2. 多线程的优点

这里本书介绍了引入多线程之后的优点:

  • 降低开发和维护成本,提升复杂应用程序的性能。
    但是其实多线程程序并不好维护,因为其异步的特性,不像同步程序所有流程都写在一起,所以这里的降低可能是与以前的更为复杂的程序做对比。

  • 提升图形用户界面的灵敏度
    这点毫无疑问,如果没有多线程,那么图形界面将充满了阻塞。

  • 在服务器应用程序中,提升资源利用率和系统吞吐率。
    Web 服务器是天然的高并发场景,一个服务器同时需要接受很多个客户端的同时访问,如果没有多线程则响应时间会大大提升,甚至不可用。

  • 提升多核处理器的利用率
    随着时代的发展,现在的 CPU 核心是越来越多了,而线程是可以真正并行在不同核心上的,而且就算在单核 CPU 上,多线程在某些场景也可以提升 CPU 的利用率,比如一个外部 I/O 需要等待回应,这个时间就可以切换到其他线程执行任务,提高 CPU 的利用率

  • 多线程程序建模比复杂的异步程序更加简单
    还是那句话,简单与复杂是对比出来的。如果一个程序中的任务类型都相同,那么比包含多种异构任务的程序要更加易于编写,测试,并且错误会更少。
    作者提出使用线程可以将复杂的异步工作流拆解为「一组」简单的同步工作流,每个流程在单独的线程中运行,并在特定的位置进行交互。

ServletRMI 框架可以帮助我们实现上述目标,我们在编写 Servlet 程序的时候并不需要了解同时处理多少个请求的具体底层逻辑,以及 Socket 输入流输出流是否被阻塞。我们使用同步的方式来处理请求,就像编写普通的单线程应用那样。

3. 多线程带来的问题

技术的发展总是伴随着问题的出现,为了提高计算机的利用率和最大化性能,操作系统出现了,此时进入多进程时代。

进程过于庞大,于是细分为线程,进一步的细分意味着对资源的更细粒度的掌控,于是 性能和利用率进一步提示,进入多线程时代。

但是多线程环境下也伴随着其特有的问题:

  • 可见性问题:CPU 缓存导致 —— 硬件层面
  • 有序性问题:编译器优化导致 —— 语言层面
  • 原子性问题:操作系统线程切换 —— 操作系统层面

这三个问题都属于安全性问题。

多线程还存在着活跃性和性能问题:

  • 当程序无法执行某行代码的时候,就出现了活跃性问题,最典型的就是死锁,多个线程的等待资源被对方持有,导致资源成功获取永远不会发生,进而处于无限的等待状态。
  • 线程的切换存在不小的开销,操作系统需要保存当前线程的执行状态和上下文,当存在大量的线程时,反而可能导致应用性能的下降。

所以这就是回答了第一个问题,什么是并发 :并发就是在操作系统中同时运行多个计算。

那么并发是怎样来的? 很明显,并发随着操作系统的出现而来,其目的就是为了提醒:「性能」 和 「利用率」。 这很容易理解,毕竟计算机是资源,我们希望最大化的榨取它的性能,让它一直能处于计算状态有所产出,之前一台计算机智能运行一个程序,多个程序就需要多个计算机,有了操作系统之后计算机可以同时运行多个程序(当然这里在多核心CPU出现之前,这里的同时也只是假象,其真正原因是 CPU 的高频率切换以及操作系统的 「时分复用」 机制)

单线程程序对比多线程程序的优势:

Java 中的并发

上面已经说了,并发的出现是因为操作系统的出现,那么 Java 作为编程语言,也是对并发这种特性有支持的,下面就聊聊 Java 中的并发。

每启动一个 main 方法, 操作系统中就会增加一个 JVM 实例,这个实例中可以启动多个线程,操作系统具体调度的就是 JVM 中的不同线程。

Java 内存模型

Java 中的内存模型设计如下(来自深入学习JVM):
image-20200713193854440

可以看到,每个线程中有自己独显的数据区域,也有线程共享的数据区域。

Java 与并发相关的类:

最直接的类

  • Thread , 这个类就是线程对应的抽象对象。
  • Object 中也有与线程相对于的方法。
  • JUC 并发包中存在着线程池相关的类

Java 中与并发相关的关键字:

  • synchronized ,我想就算你没接触过并发,应该也知道 synchronized 锁这个关键字
  • volatile ,内存可见性相关的关键字。

Java 中与并发相关的应用

  • 最直接的 Web服务器,比如 Tomcat,一个服务器要接受无数的请求,这些请求是被并发执行的,否则的话只有执行完上一个请求才能执行下一个,那么肯定无法满足用户的需求,Web 服务器应用场景是天然的并发场景
  • Android 开发,之所以没说 Swing ,是因为太古老了,我最早也接触过一些安卓开发的相关工作,当时刚入门,就接触了线程相关的概念,因为当时渲染界面只能在主线程完成,工作事件要分配的专门的工作线程,不同给的线程负责不同的工作,因为界面的点击也是并发的,可能用户点了一个按钮之后又点了另外的按钮,这些对应着不同的事件和策略。

本章内容脑图

并发简史

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

最是人间留不住,曾是惊鸿照影来。