CPU缓存结构
锁的内存语义
在JVM中,锁可以使得临界区的访问是互斥的,即保证多线程的同步访问。
而锁存在以下内存语义:
1、当一个线程获得锁的时候,会将线程本地内存置为无效,访问的共享变量必须从主内存中重新读取。
2、当一个线程释放锁的时候,会将线程本地内存中的共享变量值立即刷新到主内存中。
volatile
volatile的特性
- 可见性(对其他线程可见)
- 禁止指令重排(单线程执行指令禁止重排)
- 单个变量操作原子性 (单变量的直接赋值,a = 1)
volatile原理
轻量级的锁,被volatile修饰的共享变量会被JVM实现一系列类似于锁的内存语义。在线程对volatile变量进行读或写操作时会有不同的限制。
写操作会被编译器编译成带有lock前缀的汇编指令,而这个lock指令有两个作用:
(1)在写操作完成引起处理器将本地缓存刷新的主内存。
(2)写回主内存时使其他处理器的缓存失效(缓存一致性协议,通过锁总线或锁缓存的方式,并结合嗅探技术来判断处理器的本地缓存是否发生更改)。
volatile的内存语义
编译器通过在生成字节码指令时插入内存屏障来禁止特定类型的处理器重排序,在新的volatile内存语义定义中,是不允许volatile变量和非volatile变量进行重排序的。
(1)在每个volatile写操作的前面插入一个StoreStore屏障。
(2)在每个volatile写操作的后面插入一个StoreLoad屏障。
(3)在每个volatile读操作的后面插入一个LoadLoad屏障。
(4)在每个volatile读操作的后面插入一个LoadStore屏障。
解决伪共享
1、缓存行填充
以64位操作系统为例,一个缓存行的大小是64个字节。假设有两个volatile变量,在对a进行操作时,也可以同时对b进行操作,但是如果变量a和b同时处于同一个缓存行中,那么对其中一个变量进行操作时,其他处理器都不能对另一个变量进行操作,因为缓存一致性的原理,变量所在的缓存行会被锁定,导致其他处理器无法访问。将变量所在的缓存行填满可以将两个变量存在同一个缓存行中的概率降到0。
缓存行填充的缺点就是会造成无用的资源浪费,由于追加的对象是无用的,会严重造成缓存资源的浪费。
在JDK7之后此方式不再适用,会对无用对象进行过滤。
class Node {
//通过追加对象进行缓存行填充,15个对象60个字节,一个Node就是64个字节
Object f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12,f13,f14,f15;
volatile int state;
}
2、@sun.misc.Contended
@sun.misc.Contended是JDK8引入的注解,很好解决了缓存伪共享问题。它避免了缓存行填充带来的资源浪费的影响,同时也能达到不同共享变量不在同一个缓存行中的效果。
CAS
为什么需要CAS,CAS可以减少频繁的线程上下文切换带来的开销,通过“无锁”的形式达到线程同步效果。CAS期间会一直占用线程。
CAS必须配合volatile使用才有意义,volatile仅保证单个变量单个操作的原子性,对于复合操作并不能保证。CAS是通过机器原语来实现一组指令的原子操作,如i ++ 操作包含读取i、i + 1、i 赋值三个操作,而CAS是在循环体中比较i的旧值来确定是否可以进行写操作,而完整的一组CAS指令是原子操作,就包含旧值和新值的比较、替换操作,此过程是在硬件基础上实现的。
synchronized
synchronized原理
利用java对象头中的Mark Work存储锁的信息(以32位JVM为例)
锁状态 | 32bit | ||||
---|---|---|---|---|---|
25bit | 4bit | 1bit | 2bit | ||
23bit | 2bit | 偏向模式 | 标志位 | ||
未锁定 | 对象哈希码 | 分代年龄 | 0 | 01 | |
轻量级锁 | 指向调用栈中锁记录的指针 | 00 | |||
重量级锁 | 指向重量级锁的指针 | 10 | |||
GC标记 | 空 | 11 | |||
偏向锁 | 线程ID | Epoch | 分代年龄 | 1 | 01 |
同步代码块 monitorenter和monitorexit
异常时释放锁的字节码验证
同步方法 方法标记符:ACC_SYNCHRONIZED
synchronized使用
//对象锁角色
private final Object lock = new Object();
//同步方法
public synchronized void print2(){
}
//同步代码块
public void print() throws InterruptedException {
/**
* 这里的锁阻塞是栈模型??
*/
synchronized (lock){
System.out.println(Thread.currentThread().getName()+":get lock ...");
Thread.sleep(100);
}
}
synchronized阻塞栈模型验证
public static void main(String[] args) {
new Thread(() -> {
synchronized (monitor){
try {
Thread.sleep(20000);
System.out.println("thread-leader release...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
stackTest();
}
public static void stackTest(){
for (int i = 0; i < 10; i ++){
Thread thread = new Thread(() -> {
synchronized (monitor){
System.out.println(Thread.currentThread().getName());
}
},"thread-"+i);
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果: