第12章 Java内存模型与线程

“内存模型”可以理解为在特定的操作协议下对特定的内存或高速缓存进行读写访问的过程抽象。

Java内存模型

1 主内存与工作内存

Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。此处的变量与Java编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。

工作过程:所有的变量都存储在主内存中。每条线程还有自己的工作内存(属于线程私有,类似处理器的高速缓存),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量,线程间变量值的传递均需要通过主内存来完成。

2 内存间交互操作

Java内存模型中定义了一下8种操作来完成主内存和工作内存间的交互操作,虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的。

  1. lock
  2. unlock
  3. read
  4. load
  5. use
  6. assign
  7. store
  8. write
3 对于volatile型变量的特殊规则

在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但是由于每次使用前都要先刷新,执行引擎看不到不一致的情况。

Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。

使用volatile变量第一是保证此变量对所有线程的可见性。

volatile变量不安全场景:

  1. 多线程并发对volatile变量 i++;

由于volatile变量只能保证可见性,在不符合一下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized或java.util.concurrent中的原子类)来保证原子性。

  1. 运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值;
  2. 变量不需要与其他的状态变量共同参与不变约束。

volatile适用场景:

  1. 使用volatile变量的第二个语义是禁止指令重排序优化。

选用volatile的意义:大多数场景下volatile的总开销仍然要比锁低,我们在volatile与锁之中选择的唯一依据仅仅是volatile的语义能否满足使用场景的需求。

4 对于long和double型变量的特殊规则

目前各平台下的商用虚拟机几乎都选择把64位数据的读写操作作为原子操作来对待,因此我们在编写代码时一般不需要把用的long和double变量专门声明为volatile。

4 原子性、可见性与有序性

原子性:由Java内存模型来直接保证的原子性操作包括read、load、assign、use、store和write,我们大致可以认为基本数据类型的访问读写是具备原子性的(long和double的非原子协定几乎不会发生例外情况)

可见性:可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。除了volatile之外,synchronized和final也能实现可见性。同步块的可见性是由“对一个变量执行unlock操作之前,必须把此变量同步回到主内存中”这条规则获得的,而final关键字的可见性是指:被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递出去,那在其他线程中就能看见final字段的值。

有序性:Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized的锁操作决定了持有同一个锁的两个同步块只能串行地进入。

Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”线程和“工作内存与主内存同步延迟”现象。

6 先行发生原则

Java内存模型下一些“天然的“先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。

  1. 程序次序规则
  2. 管道锁定规则
  3. volatile变量规则
  4. 线程启动规则
  5. 线程终止规则
  6. 线程中断规则
  7. 对象终结规则
  8. 传递性

Java与线程

java线程的实现

对于Sun JDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中。

Java线程调度

调度方式有两种:

  1. 协同式线程调度
  2. 抢占式线程调度

状态转换

Java定义了5中线程状态

  1. 新建(NEW)
  2. 运行(Runable):Runable包括操作系统线程状态中的Running和Ready。
  3. 无期限等待(Waiting)
  4. 期限等待(Timed Waiting)
  5. 阻塞(Blocked)
  6. 结束(Terminated)