基本特点
1.开始时是乐观锁,当竞争激烈后转化为悲观锁。
2.开始是轻量级锁实现,如果锁被持有的时间较长,就转换成重量级锁。
3.实现轻量级锁的时候⼤概率⽤到的⾃旋锁策略
4.是不公平锁。
5.是可重入锁。
6.不是读写锁。
加锁工作过程
synchronized的加锁过程可以分为三个阶段,偏向锁、轻量级锁、重量级锁。
偏向锁:第一个加锁的线程,会进入偏向锁阶段。此时并不是真正的加锁,只是给锁对象增加一个标签,如果后续没有其他线程加锁,那么就不会真的加锁。采用了“能不加锁就不加锁的策略”,可以节约一部分资源。如果锁对象因为锁竞争而进入轻量级锁阶段,那么这个锁被释放后再被获取时,不会进入偏向锁阶段。
轻量级锁:当锁处于偏向锁阶段,并且有其他线程来竞争这个锁时,锁就会进入轻量级锁阶段(自适应的自旋锁)。
此处的轻量级锁就是通过CAS来实现。
通过CAS检查并更新⼀块内存(⽐如null=>该线程引⽤), 如果更新成功,则认为加锁成, 如果更新失败,则认为锁被占⽤,继续⾃旋式的等待(并不放弃CPU)。
重量级锁,如果锁在轻量级锁阶段时的竞争进一步加深,自旋锁所带来的cpu消耗会过多,那么锁就会进入重量级锁阶段。
此处的重量级锁就是指⽤到内核提供的mutex:
执⾏加锁操作,先进⼊内核态.在内核态判定当前锁是否已经被占⽤,如果该锁没有占⽤,则加锁成功,并切换回⽤⼾态.如果该锁被占⽤,则加锁失败.此时线程进⼊锁的等待队列,挂起.等待被操作系统唤醒.经历了⼀系列的沧海桑⽥,这个锁被其他线程释放了,操作系统也想起了这个挂起的线程,于是唤醒这个线程,尝试重新获取锁。
其他的优化
锁消除
锁在使用时,编译器+JVM判断锁是否可消除.如果可以,就直接消除。还是本着“能不枷锁就不加锁”的思想来节约损耗。
锁粗化
当锁的代码块中执行的逻辑少时,就称锁的粒度较细,反之称为较粗。
在使用粒度细的锁的时候,期望是释放锁后,有其他线程来获取这个锁。但可能没有其他线程来获取这个锁,那么JVM就会把这些粗度较细的锁粗化为一个大锁,避免频繁获取释放锁带来的开销。