synchronized的实现原理
简介
synchronized 是基于cpu的monitor临界区的上锁。
类型
类锁
synchronized(XXX.class){
// 代码块
}
所有线程无论是否创建对象,都排他竞争。
对象锁
Object obj = new Object();
synchronized(obj){
// 代码块
// 此处在线程内部创建obj,加锁毫无意义。
}
synchronized(this){
// 代码块
}
每个线程如果都创建一个对象,那么对象锁将无意义(springweb保证对象是单例,平时使用对象锁,可重入)
原理
synchornized底层实现做了多层优化,依赖于cpu提供的montior的能力。
加锁原理
临界区实现同步代码块能力,cas操作当先资源obj(synchornized(obj))设置偏向锁位设置当前线程id。
唤醒等待
synchornized同步代码块中,可以调用wait、notify来使线程等待或唤醒。此方式是montior提供的对运行线程管理的一种方式。
wait会将线程挂起,等待notify调用,重新加入锁的竞争。
锁的优化
当多线程竞争时,synchornized做了底层优化,引入偏向锁、轻量锁、重量锁。
偏向锁
当只有一个线程进入的时候,会直接将montior中的偏向锁位设置为当前线程id。
轻量级锁
当多个线程参与竞争时,会升级为轻量级锁,spin自旋加入EntrySet竞争。
EntrySet是montior的一种优化,防止过多线程直接竞争锁,而引入的一个链表集合,需要线程先进入EntrySet队列,再进行自旋竞争锁(防止多线程过多消耗资源)。
EntrySet也相当于限流的一种方式。
monitor也方便管理竞争锁线程的状态。
重量级锁
当在轻量级锁竞争失败,自旋次数达到一定阈值后,会从轻量级锁升级为重量级锁。当前线程会进入挂起阻塞状态。选择合适时机再次自旋竞争。(但是不公平出现,当当前线程进入waitSet阻塞时,新进入的线程可能比它更快速获得锁)
问题
由于CPU架构问题: L1 -> L2 -> L3 -> 内存。
多线程执行下,不同CPU核心在读取变量会先按照 L1 -> L2 -> L3 -> 内存中读取,以便于提高执行效率。由于通常情况下 L1 L2 为cpu核心独有,导致不同线程的数据出现不一致。
java给出的解决方案:
happends-before
常用:
使用volitale修饰共享变量,由jvm禁止CPU对于共享变量的重排序。
synchronized锁定代码块,进入和结束都会也会触发happends-before,并进行强制缓存数据刷新。
jdk9 AtomicRefrence 的tryAcure方法
Thread.start() 一定先于 Thread.run()等
应用
基于synchronized实现的单例
private static volatile Singleton instance = null;
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
疑惑
如果synchronized内部能保证happends-before,在释放锁时,就会把共享变量instance刷新到内存中,那么还需要volatile 修饰吗?
synchronized 能有效避免进入临界区和退出临界区共享变量的值不一致现象,进入和退出都会将共享变量的值刷新到内存,以避免重排序。
但是在代码块内部,创建对象并赋值操作,仍可能出现CPU重排序,先开辟内存空间给instance,再创建对象。此种现象会出现其他线程引用一个未初始化完成的对象。
所以volatile 是必须的。