进程
程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至cpu,数据加载至内存。在指令运行过程中还需要磁盘网络等设备。进程就是用来加载指令、管理内存、管理IO的
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
进程就可以视为程序的一个实例。大部分程序可以运行多个实例进程(例如记事本 、画图、浏览器),也有的程序只能启动一个实例进程(例如网易云音乐、360安全卫士)
线程
一个进程之内可以分为一到多个线程。
一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。
Java中,线程作为最小调度单位,进程作为资源分配的最小单位。在Windows中进程是不活动的,只是作为线程的容器
二者对比
进程基本上是相互独立的,而线程存在于内存内,是进程的一个子集。
进程拥有共享的资源,如内存空间等,仅其内部的线程共享。
进程间通信较为复杂
同一台计算机的进程通信称为IPC
不同计算机的进程通信,需要通过网络,并遵守不同的协议,例如HTTP
线程通信相对简单,因为他们共享进程内的内存,一个例子是:多个线程可以访问同一个共享变量
线程更轻量,线程上下文切换成本一般比进程上下文切换低
并发
一般将线程轮流使用CPU中的单个核心的做法称为并发,concurrent,同一时间应对(dealing with)多件事情的能力
并行
一般计算机都是多核心,多个核心同时执行线程的做法称为并行。parallel,统一时间对手做(doing)多件事情的能力。
在一般情况下,计算机的核心线程数大于核心数,当执行的线程数多余计算机的线程数时,称之为并发和并行同时存在。这种情况比较多见。
应用
设计:多线程可以让方法执行变为异步的(不要巴巴干等着方法的返回值)比如说读取磁盘文件时,假设读取花费了5秒,如果没有线程调度机制,这5秒里调用者什么都做不了,其他代码都得暂停。
结论:比如在项目中,视频文件需要转格式等操作比较费时,这时开启一个新的线程处理视频的转换,避免阻塞主线程。tomcat的异步servlet也是类似的目的,让用户线程处理耗时较长的操作,避免阻塞tomcat的工作线程。ui程序中开线程进行其他操作,避免阻塞ui线程
原理之线程运行
栈与帧
我们都知道JVM中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。
每个栈由多个帧(Frame)组成,对应着每次方法调用时所占用的内存。
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
每个线程运行时虚拟机都会为其开辟一个自己的独立栈,线程间栈帧内存是独立的。
线程上下文切换
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码 线程的 cpu 时间片用完 垃圾回收 有更高优先级的线程需要运行 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法 当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等 Context Switch 频繁发生会影响性能。
常见方法
start,run,getState
@Slf4j
public class Test5 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.debug("t1-running");
}, "t1");
System.out.println(thread.getState());//打印线程状态
thread.start();
log.debug("main-running");
System.out.println(thread.getState());//线程开启后在此打印状态
}
}
由下图可以看到在调用start前,先new了一个线程,调用后打印出runnable对象,getState方法可以查看线程状态,一个线程不能连续调用2次start方法,否则抛非法调用异常。
sleep 与 yield
sleep
1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
3. 睡眠结束后的线程未必会立刻得到执行
4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
2. 具体的实现依赖于操作系统的任务调度器 线程优先级 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用