【并发专题】从0开始深入理解并发、线程与等待通知机制(上——前置知识)

67 篇文章 3 订阅
16 篇文章 0 订阅

前言

由于Java语言“天生多线程”的特点,我们不可避免的会遇到线程竞争问题,所以,我们要学习并发编程的理由有很多,但是总的来说,就是:

  • 编写可靠的代码
  • 编写高性能代码
  • 优化用户体验
  • 充分利用 CPU 的资源
  • … …

但是在学习之前,我们需要学习一些概念。即,什么是进程和线程?

进程

我们常听说的是应用程序,也就是 app,由指令和数据组成。但是当我们不运行一个具体的 app 时,这些应用程序就是放在磁盘(也包括 U 盘、远程网络存储等等)上的一些二进制的代码。一旦我们运行这些应用程序,指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备,从这种角度来说,进程就是用来加载指令、管理内存、管理 IO 的。当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。
在这里插入图片描述
站在操作系统的角度,进程是程序运行资源分配(以内存为主)的最小单位。
总结一下,就是:

  • 进程是程序应用(Application)的一个实例
  • 进程是程序运行资源分配(以内存为主,其他磁盘、网络IO次要)的最小单位

线程

一个机器中肯定会运行很多的程序,CPU 又是有限的,怎么让有限的 CPU运行这么多程序呢?就需要一种机制在程序之间进行协调,也就所谓 CPU 调度。线程则是 CPU 调度的最小单位。
线程必须依赖于进程而存在,线程是进程中的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统资源,,只拥有在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。一个进程可以拥有多个线程,一个线程必须有一个父进程。线程,有时也被称为轻量级进程(Lightweight Process,LWP),早期 Linux 的线程实现几乎就是复用的进程,后来才独立出自己的 API。在Java 中不管任何程序都必须启动一个 main 函数的主线程; Java Web 开发里面的定时任务、定时器、JSP 和 Servlet、异步消息处理机制,远程访问接口 RM 等,任何一个监听事件,onclick 的触发事件等都离不开线程和并发的知识。
总结一下,就是:

  • 线程是CPU调度的最小单位
  • 进程包含线程。进程可以没有线程,线程不能没有进程
  • Java线程无处不在

进程间的通信

RPC我们都不陌生吧,Remote Process Comunication,也有的文章叫做Remote Process Call。这是在不通的服务器之间的进程通信使用的一种技术,需要通过网络,并且遵守某些协议。比如我们常说的Dubbo就是一个PRC框架,而Http协议也经常用在RPC上,比如:SpringCould微服务框架。
IPC,则是Inter Process Communication,内部进程通信,是指在同一台服务器的不同进程之间的通信。
那我们常见的进程间通信方式有哪些呢?

  1. 管道,分为匿名管道(pipe)及命名管道(named pipe):匿名管道可用于具有亲缘关系的父子进程间的通信,命名管道除了具有匿名管道所具有的功能外,它还允许无亲缘关系进程间的通信
  2. 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的
  3. 消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息
  4. 共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等
  5. 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段
  6. 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。同一机器中的进程还可以使用Unix domain socket(比如同一机器中 MySQL 中的控制台 mysql shell 和 MySQL 服务程序的连接),这种方式不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,比纯粹基于网络的进程间通信肯定效率更高

CPU核心数和线程数的关系

前面说过,目前主流 CPU 都是多核的,线程是 CPU 调度的最小单位。同一时刻,一个 CPU 核心只能运行一个线程,也就是 CPU 内核和同时运行的线程数是 1:1 的关系,也就是说 8 核 CPU 同时可以执行 8 个线程的代码。但 Intel 引入超线程技术后,产生了逻辑处理器的概念,使核心数与线程数形成 1:2 的关系。在我们前面的 Windows 任务管理器贴图就能看出来,内核数是 6 而逻辑处理器数是 12。
在 Java 中提供了 Runtime.getRuntime().availableProcessors(),可以让我们获取当前的 CPU 核心数,注意这个核心数指的是逻辑处理器数。获得当前的 CPU 核心数在并发编程中很重要,并发编程下的性能优化往往和 CPU 核心数密切相关
在这里插入图片描述

*上下文切换

既然操作系统要在多个进程(线程)之间进行调度,而每个线程在使用 CPU时总是要使用 CPU 中的资源,比如 CPU 寄存器和程序计数器。这就意味着,操作系统要保证线程在调度前后的正常执行,所以,操作系统中就有上下文切换的概念,它是指 CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换
上下文其实是 CPU 寄存器和程序计数器在任何时间点的内容。什么是寄存器,什么是程序计数器?

  • 寄存器:是 CPU 内部的一小部分非常快的内存(相对于 CPU 内部的缓存和 CPU外部较慢的 RAM 主内存),它通过提供对常用值的快速访问来加快计算机程序的执行
  • 程序计数器是一种专门的寄存器,它指示 CPU 在其指令序列中的位置,并保存着正在执行的指令的地址或下一条要执行的指令的地址,这取决于具体的系统

上下文切换可以更详细地描述为内核(即操作系统的核心)对 CPU 上的进程(包括线程)执行以下活动:

  1. 暂停一个进程的处理,并将该进程的 CPU 状态(即上下文)存储在内存中的某个地方
  2. 从内存中获取下一个进程的上下文,并在 CPU 的寄存器中恢复它
  3. 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程

上下文从数据层面来说,对于程序员而言,就是方法调用过程中的各种局部的变量与资源; 对于线程而言,则是方法的调用栈中存储的各类信息
引发上下文切换的原因一般包括:线程/进程切换、系统调度等等。上下文切换通常是计算密集型的,因为涉及一系列数据在各种寄存器、 缓存中的来回拷贝。就 CPU 时间而言,一次上下文切换大概需要 5000~20000 个时钟周期,相对一个简单指令几个乃至十几个左右的执行时钟周期,可以看出这个成本的巨大。

并行和并发

我们举个例子,如果有条高速公路 A 上面并排有 8 条车道,那么最大的并行车辆就是 8 辆,此条高速公路 A 同时并排行走的车辆小于等于 8 辆的时候,车辆就可以并行运行。CPU 也是这个原理,一个 CPU 相当于一个高速公路 A,核心数或者线程数就相当于并排可以通行的车道;而多个 CPU 就相当于并排有多条高速公路,而每个高速公路并排有多个车道。
当谈论并发的时候一定要加个单位时间,也就是说单位时间内并发量是多少?离开了单位时间其实是没有意义的。
综合来说:
并发 Concurrent:指应用能够交替执行不同的任务,比如单 CPU 核心下执行多线程并非是同时执行多个任务,它只是在你几乎不可能察觉到的速度不断去切换这两个任务,已达到"同时执行效果"。换句话说:只是计算机的速度太快,我们无法察觉到而已
并行 Parallel:指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话,这两件事情可以同时执行
两者区别:并发是交替执行,并行是同时执行,如下图所示。(或者我们记住,【能真正意义同时执行多个任务的叫做并行】)
在这里插入图片描述
上面的是并发,两支队伍交替进行业务操作
在这里插入图片描述
上面的是并行,两支队伍可以分别同时执行
顺便提一嘴串行,串行则是所有任务都只能在一条道上按序运行,同一时间只能执行一个任务,如下图所示:
在这里插入图片描述
总结:并行是指,同一时刻多个任务一起执行;并发是指,非常短的时间内交替执行任务。

学习总结

  1. 学习了一些计算机基础原理知识,重新认识一下进程、线程的概念
  2. 学习了CPU核心数与线程数的关系,以及CPU核心数跟逻辑核心数,还有Runtime.getRuntime().availableProcessors()的返回内容本质
  3. 学习了上下文切换的概念
  4. 学习了并行与并发的概念
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

验证码有毒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值