进程与线程
进程
- 程序由指令和数据组成,但是这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的;
- 当一个程序被执行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程;
- 进程还可以视为程序的一个实例。大部分程序可以同时运行多个实例进程。
线程
- 一个进程内可以有N个线程;
- 一个线程就是一个指令流,将指令流中一条条指令以一定的顺序交给 CPU 执行;
- Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。
进程与线程对比
- 线程存在于进程内,是进程的子集;
- 进程拥有共享的资源,如内存空间,供其内部的线程共享;
- 进程间通信较为复杂(同一台计算机的进程通信称为IPC;不同计算机之间的进程通信,需要通过网络,并遵守共同的协议)
- 线程通信相对简单,因为他们共享进程内的内存,例子:多个线程可以访问同一个共享变量;
- 线程更轻量,线程上下文切换成本一般要比进程上下文切换低。
为什么使用多线程
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;
只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
线程分为内核态、用户态
- 内核态:CPU可以访问内存所有数据,,包括外围设备,例如硬盘,、网卡、 CPU也可以将自己从一个程序切换到另一个程序;
- 用户态:只能受限的访问内存, 且不允许访问外围设备。占用CPU的能力被剥夺,CPU资源可以被其他程序获取;
- 管理开销小:创建、销毁不需要系统调用
- 切换成本低:用户空间程序可以自己维护,不需要走操作系统调度。
synchronized 是 JVM 层面上的锁的实现,可以解决可见性、顺序性和原子性。
查看进程线程的方法
Windows
- 查看进程:
tasklist
杀死进程:
taskkill /T /F /PID <PID>
,/T 表示终止指定的进程和由它启用的子进程,/F 表示强制执行Linux
查看所有进程:ps -ef
- 查看某个进程
:ps -fT -p - 杀死进程:kill -9
- 按大写H切换时候显示线程:top
-
Java
查看所有 java 进程:jps
- 查看某个 Java 进程
的所有线程状态:jstack - 来查看某个 Java 进程中线程的运行情况(图形化界面):jconsole
并行与并发概念
并发:是同一时间应对多件事情的能力; 并行:是同一时间动手做多件事情的能力;
同步与异步
同步:需要等待结果返回,才能继续运行; 异步:不需要等待结果的返回,就能继续运行;
注意:同步在多线程中还有另一层意思,是让多线程步调一致。
线程运行原理
栈与栈针
Java Virtual Machine Statcks (Java 虚拟机栈) 堆栈(stack)按照FILO(First In Last Out,后进先出)的原则存储数据。
JVM 是有堆、栈、方法区等组成,其中栈内存是在每个线程启动后,虚拟机在栈内存中分配一块栈内存;
- 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存;
- 每一个线程只有一个活动栈帧,对应着正在执行的那个方法;
线程的上下文切换
由于以下原因导致 CPU 不再执行当前的线程,转而执行另一个线程的代码
- 线程的 CPU 时间片用完了
- 垃圾回收
- 有更高优先级的线程需要运行;
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法程序;
当 Context Switch 发生时,需要由操作系统保存当前的线程状态,并恢复另一个线程的状态,Java 中对应概念就是程序计数器(Program Counter Register),它的作用是记住下一条 JVM 指令的执行地址,是线程私有的;
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回方法等;
- Context Switch 频繁发生会影响性能;
CPU与缓存行
因为CPU与内存的速度差异很大,需要靠预读数据至缓存来提升效率。 而缓存以缓存行为单位,每个缓存对应着一块内存,一般是 64byte; 缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中,CPU要保证数据的一致性,如果某个CPU核心更改了数据,其他CPU核心对应的整个缓存行必须失效。
并发解决思路
- 共享模型
- 非共享模型
synchronized 实际上是利用对象所保证了临界区代码的原则性,保证临界区代码对外是不可分割的,不会被线程切换所打断;
**
// synchronized 锁普通方法,相当于锁住的是this对象
public synchronized void methodA(){
// 方法体
}
// 锁住的是this对象
public void methodAA(){
synchronized (this){
// 方法体
}
}
// synchronized 锁静态方法,相当于锁住的是类对象
public synchronized static void methodA(){
// 方法体
}
// 锁住的是类对象
public static void methodAA(){
synchronized (Application.class){
// 方法体
}
}
变量的线程安全分析
成员变量和静态变量是否线程安全?
- 如果他们没有共享,则线程安全
- 如果他们被共享了,根据他们的状态是否能够被改变
- 局部变量是线程安全的
- 但是局部变量引用的对象则未必:
- 如果该对象没有逃离方法的作用访问,则线程安全;
-
常见的线程安全的类
String
- Integer
- StringBuffer
- Random
- Vector
- Hashtable
- java.util.concurrent 包下的类
共享模型
共享问题
synchronized
线程安全分析
Monitor
wait/notify
线程状态转换
活跃性
lock共享模型之不可变类
- JVM层面上 synchronized 的关键字
- API 层面上 lock 锁、J.U.C