基础
- 共享变量(堆空间中所有的实例域,静态域,数组元素)的访问需要同步,而局部变量不会在线程间共享,所以不存在可见性问题。
- 每一个线程都有一个私有的本地内存(抽象概念)
- 源代码到最终的指令序列执行需经过编译器重排序和处理器重排序
- JMM要求java编译器在生成指令序列时,插入特定的内存屏障以禁止特定类型的处理器重排序。
重排序
- 重排序时遵守数据依赖性(写后读,读后写,写后写)
- as-if-serial语义:不管怎么重排序,确保单线程程序的执行结果不变。
volatile的内存语义
- 可见性:对一个volatile变量的读,总是能读到对这个volatile变量最后的写入
- 原子性:对单个volatile变量的读写具有原子性(volatile++这种复合操作不具有原子性)
- A线程在写volatile变量之前
所有可见
的共享变量,在B线程读同一个volatile变量后,将立即对B线程可见.
锁的内存语义
- 线程A在释放锁之前所有可见的共享变量,在线程B获取同一个锁之后,将立刻对B线程可见。
锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语义
final内存语义
- 写final域的重排序规则禁止把final域的写重排序到构造函数之外。这点保证了对象引用为任意线程可见时,对象的final域已经被正确初始化
- 读final域的重排序规则指出,初次读对象引用与初次读该对象包含的final域之间不能重排序
- 对象引用不能在构造函数中逸出
hanppens-before规则
如果A happens before B,那么A的执行结果对B可见。(java中 并不一定表示A比B先执行,如果A与B执行的顺序对结果没有影响是可以重排序的)
- 程序顺序规则
- 监视器锁规则:对锁的解锁,happens before 随后对该锁的加锁
- volatile规则:对一个volatile变量的写happens before 随后对该volatile变量的读
- 传递性
-
start()规则
:如果线程A中执行ThreadB.start(),那么A线程的ThreadB.start() happens before 线程B中的操作 -
join规则
:如果线程A执行ThreadB.join并成功返回,那么线程B中的操作happens before A中从ThreadB.join()操作成功返回。(线程B对共享变量的改变对A线程可见)
双锁检查锁定与延迟初始化
- 有问题的demo
private static Instance ins;
public static Instance getInstance(){
if(ins == null){
synchronized(ins){
if(ins == null)
ins = new Insatance();//这一步不是原子操作
}
}
return ins;
上面问题出在 ins = new Instance()这句不是原子操作,而且其中可能存在重排序,所以 ins变量不为null时,对象可能还没有被初始化.
- 基于volatile的解决方案
对实例字段的延迟初始化
当把对象声明为 volatile时,可以禁止重排序,保证getInstance()方法返回的对象一定是已经被正确初始化了的。
- 基于类初始化的解决方案
对静态字段的延迟初始化。
public class InstanceFactory{
private static class InstanceHoler{
public static Ins = new Instance();
}
public static Instance getInstance(){
return InstanceHolder.ins;
}
}
网友评论