synchronized是如何实现有序性、可见性和原子性的
1. 有序性
有序性保证代码按预期的顺序执行,防止指令重排序带来的问题。
原理:
- 内存屏障(Memory Barrier):在进入和退出 synchronized 代码块时,JVM 会插入内存屏障。内存屏障是一种底层指令,它会强制特定顺序的内存操作。synchronized 内存屏障的作用如下:
1. 进入 synchronized 代码块时,会插入一个LoadLoad屏障,确保屏障之前的所有读操作都完成后再执行屏障之后的读操作。
2. 退出 synchronized 代码块时,会插入一个StoreStore屏障,确保屏障之前的所有写操作都完成后再执行屏障之后的写操作。
- 通过内存屏障,synchronized 能保证代码块内的指令不会因为编译器或处理器的优化而发生重排序,从而保证代码的有序性。
- 主内存和工作内存:在 JVM 中,每个线程都有自己的工作内存,线程在执行过程中会将主内存中的变量值复制到工作内存中进行操作。当一个线程退出 synchronized 代码块时,它对共享变量的修改会被刷新到主内存中;当其他线程进入 synchronized 代码块时,会从主内存中读取最新的变量值。
- 内存屏障:同样地,synchronized 的内存屏障机制也保证了内存的可见性。在进入 synchronized 代码块时,会强制从主内存中读取变量值,确保读取的是最新的值;在退出 synchronized 代码块时,会强制将修改后的变量值刷新回主内存,确保对其他线程可见。
- 锁的机制:synchronized 通过对象的监视器锁(Monitor Lock)实现原子性。每个对象在 JVM 中都有一个监视器锁,当一个线程进入 synchronized 代码块时,它会获得该对象的监视器锁,其他线程无法获得同一对象的监视器锁,只能阻塞等待。当线程退出 synchronized 代码块时,监视器锁被释放,其他线程才能获取并执行代码块中的代码。
- 锁的实现:JVM 通过锁升级机制来优化锁的性能,包括偏向锁、轻量级锁和重量级锁:偏向锁:在没有锁竞争的情况下,线程会偏向于在第一次获得锁时为其加偏向锁,后续加锁和解锁的操作都将轻量级完成。轻量级锁:在发生轻度锁竞争时,线程会尝试通过自旋获取锁,而不直接进行线程阻塞,以减少线程上下文切换的开销。重量级锁:当自旋失败或锁竞争激烈时,JVM 会将锁升级为重量级锁,使用操作系统的同步机制(如mutex),让线程进入阻塞状态,直至获得锁。
- 原子操作:synchronized 确保了代码块内的操作是原子的,即在一个线程持有锁时,其他线程无法进入该代码块,也就无法中断当前线程的操作。这样保证了代码块内的操作是不可分割的原子操作。
- 有序性:通过内存屏障防止指令重排序,确保代码按预期顺序执行。
- 可见性:通过内存屏障和主内存与工作内存的交互,确保一个线程的修改对其他线程可见。
- 原子性:通过监视器锁机制和锁的优化策略,确保代码块内的操作是不可分割的原子操作。
2. 可见性
可见性确保一个线程修改的变量对其他线程是可见的。换句话说,一个线程在 synchronized
代码块中对变量的修改,其他线程能够立即看到。
原理:
3. 原子性
原子性保证操作不可被中断,要么全部执行,要么不执行。