进程和线程
进程
当运行一个应用程序时, 操作系统就会启动进程, 执行程序的代码, 将指令加载到CPU,数据加载到内存,指令运行过程中还需要用到磁盘、网络等设备.简单说就是加载指令、管理内存、管理 IO.大多数程序可以开启多个进程, 例如记事本, 浏览器开多窗口.在操作系统角度, 进程是分配资源的最小单位.
线程
线程必须依赖进程存在, 线程是进程的一个实体, 是CPU调度的最小单位.一个进程可以拥有多个线程, 一个线程必须依赖一个主进程.线程自己没有系统资源权限, 只有运行必要的栈, 寄存器, 程序计数器, 它可以和同属于一个进程的线程共享所有资源.线程也被叫轻量级进程.
进程间通信
同计算机的进程通信称为IPC,不同计算机进程间通信叫RPC, 需要通过网络, 共同遵守的协议.像Dubbo就是一个RPC框架, http协议也经常用在RPC.
进程通信方式
- 管道
- 信号
- 消息队列
- 共享内存
- 信号量
- 套接字
CPU核心数与线程数
同一时刻, 一个CPU核心只能执行一个线程, Intel引入超线程技术后, 有了逻辑核的概念, 一个物理核心有两个逻辑核, 例如四核的机器, 有八个逻辑核, 同一时刻能运行8个线程.
在java里面, Runtime.getRuntime().availableProcessors()可以获取当前系统逻辑核数,可以用这个方法设置线程池核心数.
上下文切换
计算机的CPU是有限的, 操作系统需要在多个线程间调度,每个线程需要使用CPU的资源(寄存器, 程序计数器), 为保证程序在多线程切换过程中正常执行, 就有了上下文的概念.
上下文:
从代码角度, 是线程在方法执行中的局部变量
从JVM角度, 是栈上存的信息
切换过程:
- 暂停当前线程, 并保存上下文
- 从队列中选择下个执行线程
- 读取下个线程需要的上下文, 从CPU寄存器中恢复它
- 返回到下个线程上次中断的位置, 从程序计数器中读取, 恢复线程
上下文切换成本:
一次上下文切换大概需要5000~20000个时钟周期, 一个简单计算指令几个乃至十几个左右的时钟周期.
监控上下文切换
- vmstat: 系统整体上下文切换面板
- pidstat: 进程级别上下文切换面板
并行和并发
并行: 比如浏览器同时打开两个窗口看视频, 叫做并行
并发: 并发一般会带上时间一起说, 比如在秒杀场景中, 一秒内有一万人下单
线程启动
- 继承Thread类, 调start方法
- 实现runable接口, 传到Thread类, 调start方法
- 实现callable接口(有返回值), 传入FutureTask类(因为Thread构造方法不支持callable, FutureTask实现了runable接口), 再传到Thread类, 调start方法
启动线程有几种方式?
两种, 一种是派生自Thread类,另一种是实现Runnable接口.callable底层也是用FutureTask实现runable接口, 而线程池是一种池化技术, 管理线程的生命周期而已, 类似数据库连接池.
run和start区别?
run是当前线程执行task任务, 不能利用异步
start是调用操作系统的native的start0方法, 会进入一个队列等待操作系统分配CPU, 等分配到CPU后才会真正执行run方法, 异步.如果多次调一个线程的start方法会抛出异常.
线程终止
自然终止
run方法全部跑完或者抛异常未捕获
stop
暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop(), 已过期.
suspend: 调用后线程不会释放资源, 例如锁, 而是占着资源进入睡眠状态, 容易造成死锁问题
stop: 调用后线程强制终止, 没有机会释放资源, 可能造成文件损坏问题
中断
- thread.interrupt(): 给线程打上中断标识
- thread.isInterrupted(): 判断线程是否有中断标识
- Thread.interrupted(): 判断线程是否有中断标识, 判断完后给中断标识设置成false, 注意抛中断异常也会给中断标识设置成false
不建议手动加字段去设置中断标识, 有延迟和阻塞问题, 例如sleep, take方法阻塞时, 能自动检测到中断标识, 抛出中断异常.