八股篇(2):关于 Sychronized 那些你不得不知道的知识
Sychronized 的工作原理?
sychronized 是 Java 提供的原子性内置锁,这种内置的并且使用者看不到的锁也被成为监视器锁。
使用 sychronized 之后,会在编译之后在同步的代码块前后加上 monitorenter 和 monitorexit 字节码指令,他依赖操作系统底层互斥锁实现。它的作用主要就是实现原子性操作和解决共享变量的内存可见性问题。
执行 monitorenter 指令时会尝试获取对象锁,如果对象没有被锁定或者已经获得了锁,锁的计数器+1。此时其他竞争锁的线程则会进入等待队列中。执行 monitorexit 指令时则会把计数器-1,当计数器值为 0 时,则锁释放,处于等待队列中的线程再继续竞争锁。
从内存语义来说,加锁的过程会清楚工作内存中的共享变量,再从主内存中读取,而释放锁的过程则是将工作内存中的共享变量写回主内存。
sychronized 锁升级原理
锁升级的过程:无锁->偏向锁->轻量级锁->重量级锁。
- 无锁:这是没有开启偏向锁时的状态,在 JDK1.6 之后偏向锁是默认开启的,但是有一个偏向延迟,需要在 JVM 启动之后的多少秒才能开启,这个可以通过 JVM 参数进行设置,同时是否开启偏向锁也可以通过 JVM 擦参数进行设置。
- 偏向锁:这个是在开启偏向锁之后的锁的状态,如果还没有一个线程拿到这个锁的话,这个状态叫做匿名偏向,当一个线程拿到偏向锁的时候,下次还想要竞争锁只需要拿线程 ID 跟 MarkWord 当中存储的线程 ID 进行比较,如果线程 ID 相同则直接获取锁(相当于锁偏向这个线程),不需要进行 CAS 操作和将线程挂起的操作。
- 轻量级锁:在这个状态下线程主要是通过 CAS 实现的。线程会在自己的栈帧中建立一个锁记录,通过 CAS 尝试将对象头中的 MarkWord 替换为指向自己栈帧中锁记录的指针,如果成功,获取锁;失败,说明有竞争。
- 重量级锁:当有两个以上的线程获取锁的时候,或者线程长时间自旋还没拿到锁会升级重量级锁。因为 CAS 如果没有成功的话始终都在自旋,进行 while 循环操作,这是非常消耗 CPU 的,但是在升级为重量级锁之后,线程会被操作系统调度然后挂起,节约 CPU 资源。
锁升级过程:
- 撤销偏向锁,升级为轻量级锁。
- 失败的 B 开始自旋(原地打转等待)。
- 升级为重量级锁。B、C、D 全部挂起,等待 A 执行完由 OS 唤醒。
在 HotSpot JVM 中,锁升级是单向的(不可逆)。即:轻量级锁不能退回到偏向锁。
例外:重量级锁在 GC(全局安全点)时,如果发现锁没有被任何线程持有,可能会发生降级回到无锁状态。但在正常的业务执行流程中,我们认为它是不可逆的。
sychronized 和 ReentrantLock 的区别?
sychronized 和 ReentrantLock 都是 Java 中提供的可重入锁:
- 用法不同:sychronized 可用来修饰普通方法、静态方法和代码块,ReentrantLock 只能用来修饰代码块。
- 获取锁和释放锁方式不同:sychronized 会自动获取锁和释放锁,当进入 sychronized 的代码块会自动加锁,而离开 sychronized 的代码块会自动释放锁。而 ReentrantLock 需要手动加锁释放锁。
- 锁类型不同:sychronized 属于非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁。
- 响应中断不同:ReentrantLock 可以响应中断,解决死锁的问题,而 sychronized 不能响应中断。
- 底层实现不同:sychronized 是 JVM 层面通过监视器实现的,而 ReentrantLock 是基于 AQS 实现的。

