Synchronized 底层分析与锁的升级与降级
Synchronized底层 之 锁的升级与降级
Synchronized代码块是由一对儿monitorEnter 和 monitorExit实现的,Monitor对象是同步的基本实现单元。
在Java 6之前,Monitor的实现完全依赖操作系统的互斥锁,因为需要从用户态到内核态的切换,所以完全是一个无差别的重量级操作。
现代的JDK 中,对Monitor 进行了大刀阔斧的改进,提供了三种不同的Monitor实现,也就是常说的三种不同的锁:偏向锁、轻量级锁和重量级锁,大大改进了其性能。
升级与降级
所谓的升级与降级就是JVM优化Synchronized运行的机制,当JVM检测到不同的竞争状况时,会自动切换到合适的锁实现,这种切换就是锁的升级与降级。
当没有锁竞争出现时,默认使用偏向锁,JVM会利用CAS操作(compare and swap),在对象头上的Mark Word部分设置线程ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏向锁可以降低无竞争开销。
如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM就需要撤销(revoke)偏向锁,并切换到轻量级锁实现。轻量级锁依赖CAS操作Mark Word来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。
有的观点认为Java不会进行锁降级。实际上据我所知,锁降级确实是会发生的,当JVM进入安全点(SafePoint)的时候,会检查是否有闲置的Monitor,然后试图进行降级。
什么是安全点
Hotspot判断对象是否应被回收用可达性分析算法,即通过GC Roots美剧判定待回收的对象,寻找哪些是GC Roots方法:
- 一种是遍历方法去和栈去查找(保守式GC)
- 一种通过OopMap数据结构来记录GCRoots位置(准确式)
明显保守式成本太高,准确式优点是让虚拟机快速定位GCRoots。
OopMap数据结构: 为了准确了解某块数据是不是指针,JVM就要能判断出所有位置上的数据是不是指向GC堆里的引用,一种方法就是从外部记录下类型信息,存成映射表。Hotspot把这样的数据结构叫OopMap。
对应OopMap的位置可作为一个安全点,在执行GC操作时,所有工作线程必须停顿“Stop-The-World”,因为可达性分析必须在一个确保一致性的内存快照中进行,安全点意味着在这个点时,所有工作线程状态是确定的,JVM就可以安全的执行GC。
正是JVM在进入安全点后,会检查是否有限制的Monitor,然后试图进行降级。
Synchronized保证原子性案例
如果没有使用锁机制,我们可以看到即便是两个线程的底度并发,就非常容易碰到,former和latter不相等的情况。代码如下:
public class ThreadSafeSample {
public int sharedState;
ReentrantLock lock = new ReentrantLock();
public void nonSafeAction() {
while (sharedState < 100000) {
int former = sharedState++;
int later = sharedState;
if (former != later - 1) {
System.out.println("Observed Data Race, former = "+ former + " later = "+ later);//观察到数据竞争
}
/*synchronized (this) {
int former = sharedState++;
int later = sharedState;
if (former != later - 1) {
System.out.println("Observed Data Race, former = "+ former + " later = "+ later);//观察到数据竞争
}
}*/
}
}
public void test2() {
lock.lock();
try {
while (sharedState < 100000) {
int former = sharedState++;
int later = sharedState;
if (former != later - 1) {
System.out.println("Observed Data Race, former = "+
former + " later = "+ later);//观察到数据竞争
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException{ //interrupted:中断
ThreadSafeSample simple = new ThreadSafeSample ();
Thread threadA = new Thread() {
@Override
public void run() {
simple.nonSafeAction();
}
};
Thread threadB = new Thread() {
@Override
public void run() {
simple.nonSafeAction();
}
};
threadA.start();
threadB.start();
threadA.join();
threadB.join();
}
这是我电脑的运行结果:
将两次赋值使用Synchronized保护起来,用this作为互斥单元,就可以避免别的线程去修改sharedState
synchronized (this) {
int former = sharedState++;
int later = sharedState;
if (former != later - 1) {
System.out.println("Observed Data Race, former = "+
former + " later = "+ later);//观察到数据竞争
}
}
代码中使用Synchronized非常便利,如果是用来修饰静态方法,其等同于利用下面代码将方法体囊括起来
Synchronized(ClassName.class){}