区别
- 偏向锁:当一个线程访问对象时,它会偏向这个线程,此时对象持有偏向锁。偏向此时访问它的第一个线程,这个线程将对象头中的ThreadID改为自己的ID,之后再次访问这个对象的时候,只需要对比ID,不需要再使用CAS再进行操作
- 轻量锁:当一个对象此时为偏向锁的时候,另一个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象此时的偏向锁状态。这时对象已经存在竞争了,此时检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,而且还要继续持有偏向锁,则偏向锁升级为轻量级锁。
- 自旋锁:当一个对象此时是轻量级锁时,当前线程持有锁并且正在使用对象,而可能很快就会执完成。所以此后另一个线程可以先等待一下(自旋),并不进入阻塞状态。
- 重量锁:一个自旋超过一定次数,自旋锁会升级为重量锁。或者,此时一个线程持有锁,另一个线程在自旋,第三个线程来访,此时自旋锁也会升级为重量锁。
锁升级
锁升级的方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
- 偏向锁是JDK6中引入的一项锁优化,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。
- 如果明显存在其它线程申请锁,那么偏向锁将很快升级为轻量级锁。
- 自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
- 指的是原始的Synchronized的实现,重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。
具体过程
- 整个膨胀过程在自旋下完成;
- mark->has_monitor()方法判断当前是否为重量级锁,即Mark Word的锁标识位为 10,如果当前状态为重量级锁,执行步骤 3,否则执行步骤 4;
- mark->monitor()方法获取指向ObjectMonitor的指针,并返回,说明膨胀过程已经完成;
- 如果当前锁处于膨胀中,说明该锁正在被其它线程执行膨胀操作,则当前线程就进行自旋等待锁膨胀完成,这里需要注意一点,虽然是自旋操作,但不会一直占用cpu资源,每隔一段时间会通过os::NakedYield方法放弃cpu资源,或通过park方法挂起;如果其他线程完成锁的膨胀操作,则退出自旋并返回;
- 如果当前是轻量级锁状态,即锁标识位为 00,膨胀过程如下:(1)通过omAlloc方法,获取一个可用的ObjectMonitor monitor,并重置monitor数据;(2)通过CAS尝试将Mark Word设置为markOopDesc:INFLATING,标识当前锁正在膨胀中,如果CAS失败,说明同一时刻其它线程已经将Mark Word设置为markOopDesc:INFLATING,当前线程进行自旋等待膨胀完成;(3)如果CAS成功,设置monitor的各个字段:_header、_owner和_object等,并返回;
- 如果是无锁,重置监视器值;