为什么使用多线程
众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:
- CPU 增加了缓存,以均衡与内存的速度差异;// 导致
可见性
问题 - 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致
原子性
问题 - 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致
有序性
问题
进程与线程
进程
进程是代码在数据集合上的一次运行活动(运行程序的动态过程),是系统进行资源分配和调度的基本单位,在Java中每开启一个虚拟机即为一个进程
线程
是进程的一个执行路径,系统运行的基本单位(CPU分配的进步单位),线程无法脱离进程存在,一个进程中至少有一个线程,也可以有多个线程共享进程的资源
即分配资源以进程为单位,但CPU资源被分配到线程
守护进程
Java线程分为守护线程和用户线程,守护线程维护JVM用户线程执行,例如GC线程
线程状态
NEW | 新建 | |
RUNNABLE | 准备就绪 | |
TERMINATED | 结束 | |
BLOCKED | 阻塞 | |
WAITING | 等待 | |
TIMED-WAITING | 有限时等待 | |
running | 正在执行 |
线程操作
start | 启动 | 新建一个线程 |
run | 执行 | 执行线程 |
stop | 终止 | 直接停止线程,容易发生数据不一致问题,不建议使用 |
interrupt | 中断 | 线程接受中断通知,但是否中断由线程本身决定 |
wait | 等待 | 对象方法,使用后进程在此对象上等待,直到某个线程调用该对象的notify方法。是线程之间进行通信的有效手段 |
notify | 通知 | 参上 |
suspend | 挂起 | 废弃,挂起不释放资源 |
resume | 继续执行 | 废弃,与susend对应 |
join | 等待线程结束 | 等待目标线程执行完毕,再执行 |
yield | 谦让 | 静态方法,执行后会让出CPU,让出后继续参与CPU资源争夺 |
同步与异步
通常用来形容一次方法的调用,同步指方法调用一开始,必须等待方法返回后才能继续。异步方法调用后立刻返回,进行后面的操作,调用的方法返回由另一个线程通知调用者。
并行与并发
并行
指多个CPU同时执行任务
并发
指在交替串行执行任务
并发等级
阻塞
在其他线程释放资源之前,线程一直等待,无法执行
无饥饿
如果线程之间存在优先级,线程调度偏向满足优先级高的线程。此时低优先级的线程可能不断被高优先级线程插队,无法获得资源,即饥饿状态
无障碍
最弱的非阻塞调度,线程都可以进入共享内存区域,检测是否发生线程冲突,一旦发现线程冲突,再进行回滚
可以通过一致性标记实现
无锁
所有线程都可以尝试对共享内存进行访问
无等待
在无锁要求一个线程在有限步骤内完成之外,要求所有线程都必须在有限步内完成,排除饥饿问题。
还可以进一步分为有界无等待和线程数无等待等几种类型
并发三要素
原子性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
代码执行的基本逻辑单位
有序性
有序性:即程序执行的顺序按照代码的先后顺序执行。
涉及代码指令重排问题
可见性
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。线程1对变量i修改了之后,线程2没有立即看到线程1修改的值
锁
分类
优化
锁偏向
如果一个线程获得了锁,那么锁就进入偏向模型。当这个线程再次请求锁时,无需做任何同步操作
适用于几乎没有锁或同一个进程经常获得锁的情况
轻量级锁
将对象头部作为指针,指向持有锁的线程堆栈内部来判断线程是否持有对象锁。
自旋锁
JVM为了避免线程真实地再操作系统层面挂起,由于当前线程无法获得锁,暂时进行几次空循环
锁消除
在JIT编译时,通过对运行上下文扫描,去除不可能存在共享资源竞争地锁。
线程池
不断开启关闭线程反而增加了CPU和内存的开销。
对经常存在的线程通过线程池维护,减少开销。