你们都在用并发,小编带你了解并发的背景

写在前面

并发与操做系统的生命历程息息相关。进程的出现,使得程序状态的保存变为现实,为进程间的切换提供了可能,实现了操做系统的并发,大大提升资源利用率。虽然进程的出现解决了操做系统的并发问题,但人们对实时性又有了更高的要求。因为一个进程由若干个子任务组成,因此人们就发明了线程,让每一个线程负责一个独立的子任务,提升程序的响应灵敏度。一个进程虽然包括多个线程,可是这些线程是共同享有进程占有的资源和地址空间的。所以,虽然多线程提升了资源利用率,保证了实时性,但同时也带来了包括安全性、活跃性和性能等问题。总的来讲,进程让操做系统的并发性成为可能,而线程让进程的内部并发成为可能。编程

进程和线程的由来

操做系统中为何会出现进程?安全

提及进程的由来,咱们须要从操做系统的发展历史谈起。bash

也许在今天,咱们没法想象在不少年之前计算机是什么样子。咱们如今能够用计算机来作不少事情:办公、娱乐、上网,可是在 计算机刚出现的时候,是为了解决数学计算的问题,由于不少大量的计算经过人力去完成是很耗时间和人力成本的。 在最初的时候,计算机只能接受一些特定的指令,用户输入一个指令,计算机就作一个操做。当用户在思考或者输入数据时,计算机就在等待。显然,这样效率会很低下,由于不少时候,计算机处于等待用户输入的状态。服务器

那么,**能不能把一系列须要操做的指令预先写下来,造成一个清单,而后一次性交给计算机,计算机不断地去读取指令来进行相应的操做?**就这样, 批处理操做系统 诞生了。用户能够将须要执行的多个程序写在磁带上,而后交由计算机去读取并逐个地执行这些程序,并将输出结果写到另外一个磁带上。多线程

虽然批处理操做系统的诞生极大地提升了任务处理的便捷性,可是仍然存在一个很大的问题:并发

假若有两个任务 A 和 B,任务A 在执行到一半的过程当中,须要读取大量的数据输入(I/O操做),而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源。人们因而想,可否在 任务A 读取数据的过程当中,让 任务B 去执行,当 任务A 读取完数据以后,让 任务B 暂停,而后让 任务A 继续执行?框架

可是这样就有一个问题,原来每次都是一个程序在计算机里面运行,也就说内存中始终只有一个程序的运行数据。而若是想要 任务A 执行 I/O操做 的时候,让 任务B 去执行,必然内存中要装入多个程序,那么如何处理呢?多个程序使用的数据如何进行辨别呢?而且,当一个程序运行暂停后,后面如何恢复到它以前执行的状态呢?异步

这个时候,人们就发明了进程,用进程来对应一个程序,每一个进程对应必定的内存地址空间,而且只能使用它本身的内存空间,各个进程间互不干扰。而且,进程保存了程序每一个时刻的运行状态,这样就为进程切换提供了可能。当进程暂停时,它会保存当前进程的状态(好比进程标识、进程的使用的资源等),在下一次从新切换回来时,便根据以前保存的状态进行恢复,而后继续执行。性能

这就是并发,可以让操做系统从宏观上看起来同一个时间段有多个任务在执行。换句话说,进程让操做系统的并发成为了可能。注意,虽然并发从宏观上看有多个任务在执行,可是事实上,任一个具体的时刻,只有一个任务在占用CPU资源(固然是对于单核CPU来讲的)。学习

为何会出现线程?

在出现了进程之后,操做系统的性能获得了大大的提高。虽然进程的出现解决了操做系统的并发问题,可是人们仍然不知足,人们逐渐对 实时性 有了要求。由于一个进程在一个时间段内只能作一件事情,若是一个进程有多个子任务,只能逐个地去执行这些子任务。好比,对于一个监控系统来讲,它不只要把图像数据显示在画面上,还要与服务端进行通讯获取图像数据,还要处理人们的交互操做。若是某一个时刻该系统正在与服务器通讯获取图像数据,而用户又在监控系统上点击了某个按钮,那么该系统就要等待获取完图像数据以后才能处理用户的操做,若是获取图像数据须要耗费 10s,那么用户就只有一直等待。显然,对于这样的系统,人们是没法知足的。

**那么,可不能够将这些子任务分开执行呢?**即,在系统获取图像数据的同时,若是用户点击了某个按钮,则会暂停获取图像数据,而先去响应用户的操做(由于用户的操做每每执行时间很短),在处理完用户操做以后,再继续获取图像数据。人们就发明了线程,让一个线程去执行一个子任务,这样一个进程就包括了多个线程,每一个线程负责一个独立的子任务。这样,在用户点击按钮的时候,就能够暂停获取图像数据的线程,让 UI线程 响应用户的操做,响应完以后再切换回来,让获取图像的线程获得 CPU资源 。从而,让用户感受系统是同时在作多件事情的,知足了用户对实时性的要求。

换句话说,进程让操做系统的并发性成为可能,而线程让进程的内部并发成为可能。可是要注意,一个进程虽然包括多个线程,可是这些线程是共同享有进程占有的资源和地址空间的。进程 是操做系统进行资源分配的基本单位,而 线程 是操做系统进行调度的基本单位

多线程并发

因为多个线程是共同占有所属进程的资源和地址空间的,那么就会存在一个问题:若是多个线程要同时访问某个资源,怎么处理? 这个问题就是并发安全性问题。

此外,可能有朋友会问,如今不少时候都采用多线程编程,那么是否是多线程的性能必定就因为单线程呢?答案是不必定,要看具体的任务以及计算机的配置。好比说:对于单核CPU,若是是 CPU密集型任务,如解压文件,多线程的性能反而不如单线程性能,由于解压文件须要一直占用 CPU资源,若是采用多线程,线程切换致使的开销反而会让性能降低。可是对于好比交互类型的任务,确定是须要使用多线程的。而对于多核CPU,对于解压文件来讲,多线程确定优于单线程,由于多个线程可以更加充分利用每一个核的资源。

虽然多线程可以提高程序性能,可是相对于单线程来讲,它的编程要复杂地多,要考虑线程安全问题。所以,在实际编程过程当中,要根据实际状况具体选择。

并发简史总结

  • 早期的计算机不包含操做系统,它们从头至尾只执行一个程序,而且这个程序可以访问计算机中的全部资源。这对于昂贵且稀有的计算机资源来讲是一种浪费;
  • 操做系统的出现使得计算机能同时运行多个程序,不一样的程序都在单独的进程中运行,而且操做系统为各个独立执行的进程分配资源( eg: 经过粗粒度时间分片使程序共享资源,如 CPU 等 )。这无疑提升了计算机资源的利用率;
  • 在早期的分时系统中,每一个进程的执行都是串行的。串行编程模型的优点在于其简单性和直观性,由于它每次只作一件事情,作完以后再作另外一件。这种串行编程模型仍然存在着计算机资源利用率不高的问题
  • 促使进程出现的因素一样也促使着线程的出现。线程容许在同一个进程中同时存在多个程序控制流。线程会共享进程范围内的资源,但每一个线程都有各自的 程序计数器 、 栈 以及 局部变量 等等;
  • 线程也被成为轻量级进程。在大多数现代操做系统中,都是以线程为基本的调度单位,而不是进程。若是没有明确的协同机制,那么线程将彼此独立执行。因为同一个进程的全部线程都将共享进程的内存地址空间,所以这些线程都能访问相同的变量,这就须要实现一种比进程间共享数据粒度更细的数据共享机制。若是没有明确的同步机制来协同对共享数据的访问,将形成不可预测的结果。

线程的优点

解耦、简化程序开发

在程序中,若是咱们为每种类型的任务都分配一个专门的线程,那么能够造成一种串行执行的假象,并将程序的执行逻辑与调度机制的细节,交替执行的操做,异步 I/O 以及资源等待等问题分离开来。经过使用线程,能够将复杂而且异步的工做流进一步分解为一组简单而且同步的工做流,每一个工做流在一个单独的线程中运行,并在特定的同步位置进行交互。

Servlet 框架就是一个很好的例子。框架负责解决一些细节问题,包括请求管理、线程建立、负载平衡等,并在正确的时刻将请求分发给正确的应用程序组件(对应的一个具体Servlet)。编写 Servlet 的开发人员不须要了解有多少请求在同一时刻被处理,也不须要了解套接字的输入(出)流是否被阻塞。当调用 Servlet 的 service 方法来响应 Web请求时,能够以同步方式来处理这个请求,就好像它是一个单线程的程序。这种方式简化了组件的开发,大大下降框架学习门槛。

多线程还有助于用户界面的灵敏响应。例如,在 Android 开发中,咱们经常将网路请求或 I/O 等耗时操做单独放到一个线程中,以提升响应的灵敏度。

提升资源利用率

多处理器系统的出现,使得同一个程序的多个线程能够被同时调度到多个 CPU 上运行。所以,多线程程序能够经过提升处理器资源的利用率来提高系统的吞吐率。其实,多线程程序也有助于在单处理器系统上得到更高的吞吐率(若是程序的一个线程在等待 I/O 操做的完成,另外一个线程能够继续运行,使程序可以在 I/O 阻塞期间继续运行)。

线程带来的风险

安全性问题

在线程安全性的定义中,最核心的概念就是正确性。当多个线程访问某个类时,无论运行时环境采用何种调度方式或者这些线程将如何交替执行,而且在主调代码中不须要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。

线程不安全类示例:

@NotThreadSafe 
public class UnsafeSequence { 
 private int value;
 /** Returns a unique value. */
 public int getNext() { 
 return value++; 
 } 
} 
复制代码

虽然 递增运算 “value++” 看上去是单个操做,但实际上它包含三个独立的操做:读取 value, 将 value 加 1,并将计算结果写入 value。因为运行时各个线程执行顺序的不肯定性,可能这段代码在不一样线程的调用中返回相同的数值

活跃性问题

活跃性问题关注的是:某件正确的事情最终会发生。致使活跃性的问题包括死锁、饥饿等。

性能问题

性能问题关注的是:正确的事情可以尽快发生。性能问题包括多个方面,例如响应不灵敏,吞吐率太低,资源消耗太高等。在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另外一个线程时,就会频繁出现上下文切换操做(Context Switch),这种操做会致使 CPU 时间更多的花在线程调度上而非线程的运行上。

线程无处不在

在 Java 中,一个应用程序对应着一个JVM实例(JVM进程)。Java采用的是 单线程编程模型 ,即在咱们本身的程序中若是没有主动建立线程的话,只会建立一个线程,一般称为主线程。可是要注意,虽然只有一个线程来执行任务,不表明JVM中只有一个线程,JVM实例在建立的时候,同时会建立不少其余的线程(好比垃圾收集器线程)。因为Java采用的是单线程编程模型,所以在进行UI编程时要注意将耗时的操做放在子线程中进行,以免阻塞主线程(在UI编程时,主线程即UI线程,用来处理用户的交互事件)。

public class Test {
 public static void main(String[] args) {
 // 获取运行当前代码的线程的名字
 String curThreadName = Thread.currentThread().getName(); 
 System.out.println(curThreadName);
 }
}
复制代码

小结

  1. 进程是对运行时程序的封装,能够保存程序的运行状态,实现操做系统的并发;
  2. 线程是进程的子任务,保证程序的实时性;
  3. 进程是操做系统资源的分配单位,线程是CPU调度的基本单位;
  4. 进程让操做系统的并发性成为可能,而线程让进程的内部并发成为可能。
相关文章
相关标签/搜索