线程的实现
Java内存模型(与Java内存区域中的Java堆、栈、方法区不是一个层次)
- 屏蔽硬件和操作系统的内存访问差异,实现跨平台的一致内存访问效果
- 主要定义各个变量(不包含局部变量、方法参数等不会被共享,不存在竞争问题的数据)的访问规则
- 所有变量存储在主内存中,对应于物理硬件主内存
- 每条线程有自己的工作内存,对应处理器高速缓存,线程对变量的操作必须在工作内存中进行,不能直接读写主存变量
- Java线程<->工作内存<->save & load操作<->主内存
- 内存间交互操作
- lock&unlock:作用于主内存变量
- read&load:读取主内存的值传递到工作内存,将得到的变量值放入工作内存变量副本中
- use&assign:将工作内存的变量值传递给执行引擎执行操作,将执行结果赋给工作内存变量
- store&write:将工作内存的值传递到主内存,将变量值放入主内存变量中
- 以上操作成对出现,顺序但不一定连续执行
并发&同步
- volatile:最轻量的同步机制
- 保证变量对所有线程的可见性,修改后立即存入主内存,每次读取都从主内存中刷新读入
- 运算结果不依赖变量的当前值,只有单一线程修改变量的值
- 变量不需要与其他的状态变量共同参与不变约束
- 禁止指令重排序优化
- DCL(double check lock实现单例模式)
- 保证变量对所有线程的可见性,修改后立即存入主内存,每次读取都从主内存中刷新读入
- 并发过程中需要考虑的三个特征
- 原子性: 上文的交互操作指令具备原子性,更大范围的原子性由synchronized定义同步块(monitorenter&moniterexit对应lock&unlock,之间的操作具备原子性)
- 可见性:volatile、final、synchronized
- 有序性: volatile、synchronized
- 先行发生原则(与时间上的先发生没有必然关系)
- 天然的先行发生关系,如果两个操作不满足这些,则无顺序性保障,可随意重排序
- 程序次序规则,写在前面的操作先发生(控制流顺序而不是程序代码顺序)
- 管程锁定规则:unlock发生在后面对同一个锁的lock之前
- volatile变量规则:对volatile变量的写操作先于读操作
- 线程启动规则:start()先行发生于此线程的每个操作
- 线程终止规则:线程的所有操作先行发生于终止检测
- 线程中断规则:interrupt()先行发生于检测到中断事件的发生
- 对象终结规则:对象初始化先行发生于finalize()方法
- 传递性:A先于B,B先于C,则A先于C
-Java线程
- 天然的先行发生关系,如果两个操作不满足这些,则无顺序性保障,可随意重排序
- 线程的实现方法
- 内核线程(KLT, Kernel-Level Thread)实现
- 由内核通过操纵调度器(Scheduler)来完成线程切换
- Program -> multi-LWP(low weight process,通常所说的线程)-> KLT(LWP与KLT1:1对应) ->scheduler -> multi-CPU
- 系统调用,需要在用户态和内核态来回切换,代价高
- LWP与KLT1:1对应,消耗内核资源,一个系统支持的LWP数量有限
- 操作系统为每个进程分配的内存有限:32位Windows系统限制每个进程的内存为2GB,从中减去最大堆、方法区、虚拟机进程消耗等剩余的内存分配给虚拟机栈和本地方法栈,而虚拟机栈和本地方法栈是线程私有,总量一定,线程越多,虚拟机栈大小越小。
- 用户线程(UT,user thread)实现
- 不需要切换内核态,快速,低消耗,支持大规模线程
- 所有线程操作都需要用户程序自己处理,复杂,实际应用较少
- 混合实现
- Java线程的实现:不同平台上的实现方法不确定
- 内核线程(KLT, Kernel-Level Thread)实现
- 线程调度
- 协同式线程调度:A线程工作完成后,主动通知系统切换另一个线程
- 抢占式线程调度:系统为每个线程分配执行时间,A线程的时间用完后,即便工作没有结束,系统也会切断线程A
- Java采用的是抢占式+设置线程优先级(优先级并不太靠谱)
- 状态转换
- 新建
- 运行
- 无限期等待
- 限期等待
- 阻塞
- 结束
- 线程安全和锁优化
- 关于这一点的知识将在之后阅读多线程相关专门书籍时加以摘录
- volatile:最轻量的同步机制