第一章 并发编程的挑战
volatile
- lock前缀指令会引起处理器缓存会写到内存 缓存一致性机制
- 一个处理器的缓存会写到内存会导致其他处理器的缓存无效
追加字节可以提升性能
synchronize
monitorenter和monitorexit
数组3个字宽存储对象头 其他2字宽
偏向锁:加锁和解锁不需要额外的消耗。如果存在锁竞争会带来额外的锁撤销的消耗。
轻量级锁:竞争的线程不会阻塞,提高了程序的响应速度。如果始终得不到锁竞争的线程,会自旋消耗CPU
重量级锁:线程不会使用自旋,不会消耗CPU。线程阻塞,响应时间缓慢。
Mark Word中存储着锁
原子操作
总线锁
缓存锁定
CAS实现原子操作的三大问题
1aba问题 2循环时间长开销大 3只能保证一个共享变量的原子操作
除了偏向锁 jvm实现锁的方式都使用了循环CAS
第三章 Java内存模型
线程间通信 共享内存和消息传递
源代码到指令序列的重排序
- 编译器优化的重排序
- 指令集并行的重排序
- 内存系统的重排序
jmm通过内存屏障指令来禁止特定类型的处理器重排序。
storeload barriers同时具有其他3个屏障的效果。确保store1数据对其他处理器变得可见。先于load2及所有后续装载指令的装载。storeloadbarrieers会使该屏障之前的所有内存访问指令完成后,才执行该屏障之后的内存访问指令。
happens-before
两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个
操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一
个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)。
happens-before的定义很微妙,后文会具体说明happens-before为什么要这么定义。
as-if-serial
按程序的顺序
在不改变程序执行结果的前提下,尽可能提高并行度。
顺序一致性
两大特性:1线程的所有操作必须按照程序的顺序执行 2每个操作必须是原子执行且对所有线程可见
只有当前线程把本地内存中写过的数据刷新到主内存之后,这个写操作才能对其他线程可见。
加入synchronize后,临界区重排序。
JMM不保证对64位的long型和double型变量的写操作具原子性,而顺序一致性模型保证对所有的内存读/写操作都具有原子性。long double的读操作必须具有原子性,写操作拆分成两个32位的写操作。
volatile
对一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入。
对任意单个volatile变量的读写具有原子性,但类似volatile++这种复合操作不具有
storestore屏障 volatile写 storeload屏障
volatile读 loadload屏障 loadstore屏障
锁的内存语义
为何2happens-before 5
CAS 具有volatile读和volatile写的内存语义
源代码 lock前缀
公平锁和非公平锁
- 释放的时候都要写一个volatile变量state
- 公平锁获取的时候首先去读volatile变量
- 非公平锁获取的时候,首先用cas更新volatile变量的值,这个操作同时具有volatile写和读的内存语义
final域规则的重排序
写:jmm禁止编译器把final域的写重排序到构造函数之外。
读:在读一个对象的final域之前,一定先读包含这个final域的对象的引用。
在构造器函数内对一个final引用的对象的成员域的写入,与随后在构造器外把这个被构造对象的引用赋值给一个引用变量,这两个操作间不能进行重排序。
public class FinalReferenceExample {
final int[] intArray; // final是引用类型
static FinalReferenceExample obj;
public FinalReferenceExample () { // 构造函数
intArray = new int[1]; // 1
intArray[0] = 1; // 2
}
public static void writerOne () { // 写线程A执行
obj = new FinalReferenceExample (); // 3
}
public static void writerTwo () { // 写线程B执行
obj.intArray[0] = 2; // 4
}
public static void reader () { // 读线程C执行
if (obj != null) { // 5
int temp1 = obj.intArray[0]; // 6
}
}
}
c能看到a的写入,看不到b的写入。
保证被构造对象的引用在构造函数中没有溢出。
JMM设计
happens-before
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个之前。
- 如果重排序之后的执行结果,与按happens-before关系执行的结果一致,那么重排序不非法。
- volatile的读写
- 线程start
- join
双重检查锁定与延迟初始化
public class DoubleCheckedLocking { // 1
private static Instance instance; // 2
public static Instance getInstance() { // 3
if (instance == null) { // 4:第一次检查
synchronized (DoubleCheckedLocking.class) { // 5:加锁
if (Instance == null) // 6:第二次检查
instance = new Instance(); // 7:问题的根源出在这里
} // 8
} // 9
return instance; // 10
} // 11
}
memory = allocate(); // 1:分配对象的内存空间
instance = memory; // 3:设置instance指向刚分配的内存地址
// 注意,此时对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象
两个解决方案:
- 不允许2和3重排序
- 允许2和3重排序,但不允许其他线程看到这个重排序。
1->volatile
2->类初始化(类加载的锁)
when类初始化
- T是一个类,而且一个T类型的实例被创建。
- T是一个类,且T中声明的一个静态方法被调用。
- T中声明的一个静态字段被赋值。
- T中声明的一个静态字段被使用,而且这个字段不是一个常量字段。
- T是一个顶级类,而且一个断言语句嵌套在T内部被执行。
网友评论