Java虚拟机规范试图定义一种Java内存模型来消除不同硬件和操作系统的差异。JDK1.5(JSR-133)开始,Java内存模型终于算是开始完善起来。Java内存模型主要是目的是定义程序中可线程共享的变量的访问规则。它规定所有变量都存在主内存,每个线程有自己的工作内存,存储的是使用的变量的主内存拷贝。线程不能直接操作主内存中的变量,互相间也不能访问工作内存。交换、传输数据只能经过主内存。
内存间交互操作
内存模型定义了8种操作:
- lock : 独占主内存变量
- unlock
- read : 主内存传输到工作内存
- load : 主内存read的变量放到工作内存副本中
- use : 工作内存中的变量传递给执行引擎
- assign : 把执行引擎的一个赋值给工作内存中的变量
- store : 工作内存变量传递到主内存
- write : 把store的值写入主内存变量中
- read和load、store和write不能单一出现,即主内存中read了,工作线程必须load不能不接受。
- 不允许丢弃最近的assign操作。即必须写回主内存
- 不允许无原因的写回主内存
- 新变量只能在主内存诞生
- 一个变量同时只能被一个线程lock。可以lock多次,同等数量的unlock可以解除锁定。
- 没lock的不能unlock。也不能unlock一个其他线程lock的变量
- 对变量lock会清空工作线程中该变量副本,执行引擎使用钱需要重新load或assign初始化值。
- unlock前必须写回主内存
- 保证此变量对所有线程的可见性,一个修改,其他的立即可见
- 禁止指令重排。通过内存屏障实现。
- 原子性:8个操作都是原子的,可以认为基本数据类型的读写都是原子的。更大范围的需要,提供了lock和unlock,反映到Java代码就是synchronized。
- 可见性:Java内存模型是通过变量修改后写回主内存,读取前从主内存刷新的方式保证可见性。volatile的规则就是保证了这两个操作的即时性。除了volatile还有两个可以实现,synchronized和final。
- 同步块的可见性是通过上面的8条规则的倒数两条获得的。
- final的可见性是通过“final字段在构造其中一旦初始化完成,并且构造器没有把final的引用传递出去,其他线程就能看到final字段的值”获得的。
- 有序性:线程内看,操作是有序的;线程看另外一个线程,所有操作都是无序的(指的是指令重排、主内存和工作内存同步延迟)。Java提供volatile和synchronized保证有序性。volatile本身就禁止重排。synchronized则是由第五条规则获得的顺序能力,它使得一个同步块只能被线程串行的执行。
如果java内存模型所有的有序性都靠volatile和synchronized保证,有些操作将会很繁琐和困难。但是实际中,并没有感觉到,因为Java有一个happens-before原则。下面是Java内存模型中纯天然的默认happens-before关系,无需任何措施,就可以使用。可以作为推断两个操作会不会被打乱顺序的依据。
- 单线程内有序
- unlock操作happens-before后面对同一个锁的lock
- Thread的start操作happens-before该线程的所有操作
- 线程所有操作happens-before线程的终止监测
- 线程的interrupt方法happens-before被中断线程的代码检测到中断的发生。
- 对象的初始化完成happens-before它的finalize方法的开始
- A happens-before B 且 B happens-before C,则A happens-before C
线程实现有三种方式:
- 轻量级线程:1对1模型,利用线程的调度功能。但开销大,需要内核态和用户态切换。
- 用户线程:开销小,但是自己实现调度,管理复杂。1对多。
- 混合:多对多。
Java定义线程的五种状态:
- 新建
- 运行:正在执行或等待cpu分配时间
- 无限期等待:不会被分配时间,只能等唤醒
- 没有超时的wait
- 没有超时的join
- LockSupport.park()
- 限期等待:
- 有超时的wait、join
- sleep
- LockSupport.parkNanos()
- LockSupport.parkUntil()
- 阻塞:等待获得一个排他锁
- 结束