0、内存模型
内存模型,可以理解为特定操作协议下,对指定的内存或高速缓存进行读写访问的过程抽象。C/C++直接使用物理硬件和操作系统的内存模型,会有不同平台的差异性。
1、Java内存模型
参考书籍:深入理解Java内存模型
Java虚拟机规范试图定义一种Java内存模型,来屏蔽掉不同平台的差异。
1)主存与工作内存
主内存与工作内存与Java内存区域中的Java堆、栈、方法并不是一个层次的内存划分(抽象方式不同),可以勉强把主内存看做Java堆中对象的实例数据部分(对象中其实还有GC标志、年龄、同步锁等),把工作内存看做虚拟机栈中的部分区域。
2)内存间交互操作
8种原子性操作
执行以上8种操作时,必须满足以下规则:
3)volatile型变量
保证可见性、禁止重排序,没有原子性
每次修改完某变量的值就立马同步回内存,每次要使用该变量时都要从主内存刷新。
volatile的使用场景
4)long和double型变量的特殊规则
JMM允许虚拟机将没有被volatile修饰的64位数据的读写操作分为两次32位的操作来进行,即不保证64位数据的原子性。但是,目前JVM都会把long和double数据的操作实现为原子性的,来避免线程同步问题
5)原子性、可见性、有序性
- 原子性:JMM保证的8种原子性操作,syncronized保证更大的原子性操作
- 可见性:一个线程修改了一个变量的值,对于其他线程是立即可见的,因为该线程会立即把修改后的值同步回内存(volatile)。syncronized和final都具备可见性。同步块在对一个变量unlock之前会先把该变量同步回主内存。
- 有序性
6)先行发生原则
判断数据是否存在竞争、线程是否安全。
2、Java与线程
1)线程的实现
a、使用内核级线程实现
内核线程(Kernel-Level Thread, KLT),由内核来完成线程切换。一般不会直接使用内核线程,而失去使用内核线程的高级接口—轻量级进程(Light Weight Process, LWP),即通常意义上所讲的线程。每个轻量级进程都由一个内核级线程支持,这种轻量级进程和内核线程之间1:1的关系称为一对一线程模型。
局限性:轻量级进程有内核线程支持实现,各种操作要进行系统调度,在用户态与系统态之间来回切换代价极高。而且,内核级线程在内核中是有限的。
b、使用用户线程实现
在用户态实现,快速且低消耗。进程与用户线程为1对N的关系。
局限性: 没有内核支援,线程的 创建、切换、调度有用户自己实现,“阻塞”、“如何将线程映射到多处理器系统的其他处理器上”等问题较难解决,用户线程实现的程序较为复杂。
c、用户线程加轻量级进程混合实现
在一对一的轻量级进程上,创建多个线程,用户线程与轻量级进程N:M关系
d、Java线程的实现
Windows和Linux版采用一对一模型,Solaris平台既可以采用一对一模型,也可采用多对多模型。
2)Java线程调度
线程的调度主要有两种方式:
- 协同式调度:执行时间由线程自己来控制。实现简单,但是当此线程发生死循环时,就会造成系统不稳定,因为他永不让出处理器。
- 抢占式调度:由系统分配时间。线程有一定自主权:a、让出时间,Thread.yield;b、“建议”优先执行,设定优先级。
Java采用抢占式调度。
3)线程状态转换
五种状态:
1. 新建(New)
2. 运行(Runnable)
3. 无限期等待(Waiting):不会被分配CPU执行时间,等待显式唤醒,否则无限期等待。有以下方法:
4. 限期等待(Timed Waiting):不会被分配CPU执行时间,一定时间内会由系统自动唤醒,有以下方法:
5. 阻塞(Blocked):在等待获得一个排它锁。
6. 结束(Terminated)
线程状态转换图