JAVA内存模型
我们知道计算机在实现多线程操作时,不仅仅需要CPU处理器并发操作,更重要的是并发时,CPU寄存器、高速Cache同主内存间的数据交互。只有保证了这些才能保证安全的线程操作。C语言需要直接操作硬件来完成这些,所以它一旦更换机子就不行了。那么JAVA是如何保证的呢?
JAVA虚拟机通过内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让JAVA程序在各种平台下都能达到一致的并发效果。1.5版本的JAVA实现了JSR-133,JAVA内存模型就已经很不错啦。
主内存和工作内存
JAVA的内存模型尽管屏蔽了硬件信息,但是我们也是可以将至对应到硬件上的。如,每个线程都有自己的工作内存,对应处理器的高速缓存,保存了线程使用到的变量的主内存副本,线程对变量的操作都只能在工作内存中发生,不能直接在主内存中操作;主内存,所有的变量都保存其中,对应到计算机的内存上。
需要注意的是,这种内存划分方式不同于堆、栈、方法区的划分。
内存间交互操作
JMM定义了8种操作来完成,工作内存与主内存的数据交互。
- lock(锁定):作用于主内存,用于标识一个变量被一个线程独占
- unlock(解锁):同样作用于主内存,用于释放被锁定的变量
- read(读取):用于主内存,把一个变量的值从主内存读入工作内存
- load(载入):作用于工作内存,用于将read操作从主内存得到的变量值放入工作内存的变量副本中
- use(使用):作用于工作内存,把工作内存中的一个变量值传递给执行引擎
- assign(赋值):作用于工作内存,把执行引擎的值赋值给工作内存
- store(存储):作用于工作内存,把工作内存的一个变量值传送到主内存中
- write(写入):作用于主内存,把store操作从工作内存中得到的值写入主内存中
注意:read和load、store和write是成双出现的,不要求连续执行,但必须保证执行顺序。
Volatile
volatile是java提供的轻量级的同步机制,JMM为它设置了一些特殊的规则。
1)保证变量对所有线程可见性。
线程在工作内存中对变量进行修改后,会立即更新到主内存中。并且每次在工作内存中要use变量前,都会从主内存中read并load最新的变量值。为了保证所有线程可以取到最新的值,没有使用lock操作。
变量操作,不具有原子性。
2)禁止语句重排优化
虚拟机为了优化效率,会重新对程序的执行顺序进行调整,但这里不会。
long和double
JMM的8种操作都具有原子性,但是对于long和double,虚拟机允许在没有被volatile修饰的情况下将load、store、read、write操作分为2个32位的操作。
可见性
volatile、synchronized和final关键字都可以保证线程的可见性。其中:
volatile:保证新值能立即同步到主内存中,以及每次使用前从住内存读取最新值
synchronized:对一个变量执行unlock操作前,必须将此变量同步回主内存。
final:被final修饰的变量一旦在构造器中初始化完成,并且构造器没有把this的引用传递出去,那么其他线程就能看到final修饰的值。
JAVA与线程
线程实现
- 使用内核线程实现
内核线程(KLT)是由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。
- 使用用户线程实现
线程不是内核线程就是用户线程。所有的操作都需要用户自己来实现……想想都觉得可怕
- 混合实现
用户和内核混合实现,可以是M:N的形式,Unix系统就这样搞
- JAVA线程的实现
java线程模型是基于操作系统原生线程模型的,所有操作系统支持的线程实现模型决定了java的线程实现模型。
java线程调度
有2中调度方式:协同式调度和抢占式调度。
对于协同式调度,线程的执行实践由线程自己控制,线程执行完成后通知系统切换到另外一个线程上
抢占式,每个线程由系统来分配执行时间。java就是抢占式。
java线程状态
- 新建
- 运行
- 无限期等待
- 限期等待
- 阻塞
- 结束