自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,只需让线程执行一个忙循环(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
自旋等待不能代替阻塞,自旋等待本身虽然避免了线程切换的开销,但是要占用处理器时间,因此如果锁被占用时间很短,自旋等待效果就会非常好,但如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,不会有任何有用的工作,反而会带来性能上的浪费。
偏向锁
在没有实际竞争的情况下,还能够针对部分场景继续优化。如果不仅仅没有实际竞争,自始至终使用锁的线程都只有一个,那么维护轻量级锁都是浪费的。偏向锁的目标是,减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要执行一次CAS。偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此只需要在Mark Word中CAS记录owner(线程ID),记录成功则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本地直接获得锁;否则说明有其他线程竞争,膨胀为轻量级锁。
特点:只有等到线程竞争出现才释放偏向锁,持有偏向锁的线程不会主动释放偏向锁。之后的线程竞争偏向锁,会先检查持有偏向锁的线程是否存活,如果不存活,则对象变为无锁状态,重新偏向;如果仍存活,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
轻量级锁
轻量级锁的目标就是减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。
重量级锁
底层通过操作系统的mutex lock实现。等待锁的线程会被阻塞,由于Linux下Java线程与操作系统内核态线程一一映射,所以涉及到用户态和内核态的切换、操作系统内核态中的线程的阻塞和恢复。这种方式的成本非常高,因此后来称这种锁为“重量级锁”。synchronized就属于这种锁。
用户态 和 内核态 ???
synchronized关键字并非一开始就该对象加上重量级锁,也是从偏向锁,轻量级锁,再到重量级锁的过程。这个过程也告诉我们,假如我们一开始就知道某个同步代码块的竞争很激烈、很慢的话,那么我们一开始就应该使用重量级锁了,从而省掉一些锁转换的开销。
互斥锁(重量级锁)也称为阻塞同步、悲观锁
重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也称为互斥锁
为什么重量级线程开销很大的?
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
,