synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。
jdk5 以前 —重量级锁
synchronized 只有重量级锁,Synchronized是通过对象内部的一个叫做 监视器锁 (Monitor)来实现的。
但是 监视器锁本质又是依赖于底层的操作系统的 互斥锁 Mutex Lock来实现的。
并且 操作系统实现线程之间的切换这就需要从用户态转换到内核态,这个成本非常高,
状态之间的转换需要相对比较长的时间,这就是为什么Synchronized**效率低`**的原因。
因此,这种依赖于操作系统的互斥锁 Mutex Lock实现的锁我们称之为 “重量级锁”。
JDK6 开始 —偏向锁、轻量锁
对synchronized 进行优化,增加了自适应的 CAS自旋、锁消除、锁膨胀、偏向锁
、轻量级锁
这些优化策略。
锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级
在 JDK 1.6 中默认是开启偏向锁和轻量级锁的,可以通过-XX:-UseBiasedLocking来禁 用偏向锁。
偏向锁的引进,因为HotSpot作者经过研究实践发现,
在大多数的情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。
在线程持有偏向锁之后,后续对同步代码块的访问都不需要获取锁、释放锁。
偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下(即线程A 尚未执行完同步代码块,线程B发起了申请锁的申请),则一定会转化为轻量级锁或者重量级锁。
"偏向"的意思是,偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),
因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),
如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;
否则,说明有其他线程竞争,膨胀为轻量级锁。
引入轻量级锁的目的其实是为了避免使用重量级锁。
通过CAS 自旋,避免了短时间内对线程进行阻塞、唤醒,因为线程的阻塞、唤醒对应着操作系统的用户态和内核态的切换,从而节省资源,提高程序运行的性能。
其他必备知识
// 执行一个无意义的循环,目的就是等代机会去竞争到锁
while(true){
//空的
//一个:每次自旋的时间
//另外一个:自旋的次数
}
避免了短时间内对线程进行阻塞、唤醒,因为线程的阻塞、唤醒对应着操作系统的用户态和内核态的切换,从而节省资源,提高程序运行的性能。
锁的信息是存放在对象头的 Mark word 中,Mark word 根据锁的状态,存储对应的锁的信息
synchronized 与 锁升级:由对象头中的 Mark Word 根据 锁标志位 的不同而被复用及锁升级策略。
偏向锁:MarkWord 存储的是 偏向的线程ID;
轻量锁:MarkWord 存储的是 指向线程栈
中Lock Record的指针;
重量锁:MarkWord 存储的是 指向 堆
中的 monitor对象的指针;
无锁状态
偏向锁状态
轻量级锁状态
重量级锁状态
【强制】高并发时,同步调用应该去考量 锁的性能损耗
。
能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。
CAS次数不同、是否主动释放锁
轻量级锁每次申请、释放锁都至少需要一次CAS,而偏向锁只有初始化时需要一次CAS.
偏向锁,只偏向于第一个访问的线程。在这个线程(线程A)第一次来访问同步块时,会使用CAS,更新对象头的ThreadID为偏向线程的id。后续的访问,只需要比较threadId是否相同,不需要CAS操作。且这个偏向的线程是不会主动十分锁的,除非出现线程来竞争。
当第二个线程(线程B)过来访问时,他并不知第一个线程已经存在。所以这第二个线程以为它是被偏爱的,它也想向偏向线程那样使用CAS初始化对象头的ThreadID,发现失败了。
说明对象锁已经被其他线程占用,出现线程竞争。
检查原来持有该对象锁的线程是否依然存活,
如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程。
如果线程还存活,则检查线程是否在执行同步代码块中的代码,如果是,则升级为轻量级锁,进行CAS竞争锁。
轻量级锁的CAS 是每次申请锁都需要执行的。
(1) 修饰实例方法: 给当前对象实例加锁
synchronized void method() {
//业务代码
}
(2) 修饰静态⽅法: 也就是给当前类加锁
synchronized void staic method() {
//业务代码
}
(3) 修饰代码块
synchronized(this|object|xx.class) {
//业务代码
}
单例模式的双重检索也是使用单例
/* 双重检验锁 */ public class Singleton{ private Singleton(){}//构造器私有化,防止new,导致多个实例 private static volatile Singleton singleton; public static Singleton getInstance(){//向外暴露一个静态的公共方法 getInstance //第一层检查 if(singleton == null){ //同步代码块 synchronized (Singleton.class){ //第二层检查 if(singleton == null) { singleton = new Singleton(); } } } return singleton; } }
如果本文对你有帮助的话记得给一乐点个赞哦,感谢!