资料内容:
基础概念
在正式学习 Java 的并发编程之前,还有几个并发编程的基础概念我们需要
熟悉和学习。
进程和线程
进程
我们常听说的是应用程序,也就是 app,由指令和数据组成。但是当我们不
运行一个具体的 app 时,这些应用程序就是放在磁盘(也包括 U 盘、远程网络
存储等等)上的一些二进制的代码。一旦我们运行这些应用程序,指令要运行,
数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中
还需要用到磁盘、网络等设备,从这种角度来说,进程就是用来加载指令、管理
内存、管理 IO 的。
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个
进程。
进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程
(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如
网易云音乐、360 安全卫士等)。显然,程序是死的、静态的,进程是活的、动态
的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进
程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由
你启动的进程站在操作系统的角度,进程是程序运行资源分配(以内存为主)的最小单位。
线程
一个机器中肯定会运行很多的程序,CPU 又是有限的,怎么让有限的 CPU
运行这么多程序呢?就需要一种机制在程序之间进行协调,也就所谓 CPU 调度。
线程则是 CPU 调度的最小单位。
线程必须依赖于进程而存在,线程是进程中的一个实体,是 CPU 调度和分
派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不
拥有系统资源,,只拥有在运行中必不可少的资源(如程序计数器,一组寄存器和栈),
但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。一个进程可
以拥有多个线程,一个线程必须有一个父进程。线程,有时也被称为轻量级进程
(Lightweight Process,LWP),早期 Linux 的线程实现几乎就是复用的进程,后来
才独立出自己的 API。
Java 线程的无处不在
Java 中不管任何程序都必须启动一个 main 函数的主线程; Java Web 开发里
面的定时任务、定时器、JSP 和 Servlet、异步消息处理机制,远程访问接口 RM 等,
任何一个监听事件,onclick 的触发事件等都离不开线程和并发的知识。
大厂面试题:进程间的通信
同一台计算机的进程通信称为 IPC(Inter-process communication),不同计
算机之间的进程通信被称为 R(mote)PC,需要通过网络,并遵守共同的协议,比
如大家熟悉的 Dubbo 就是一个 RPC 框架,而 Http 协议也经常用在 RPC 上,比如
SpringCloud 微服务。
大厂常见的面试题就是,进程间通信有几种方式?
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 核心数密切相关。