目录

 

自旋锁

自适应自旋锁

锁消除

锁粗化

锁升级


自旋锁

    因为线程的阻塞到唤醒需要CPU从用户态转为内核态,这样所需要的资源是比较大的,如果频繁的去阻塞和唤醒这对CPU是一件压力很大的事情,并且有时候会发现对象锁的状态只会持续一段时间,为了这段比较短的时间去让CPU从用户态转化为内核态是一件得不偿失的事情。所以基于这个就出现了自旋锁。

    那么,什么是自旋锁呢?自旋锁就是在线程获取不到这个锁的情况下,并不会直接进入阻塞状态,而是先去自旋几次(就是在这循环,啥也不干),看持有锁的线程会不会比较快的释放锁。如果能释放的话那就停止自旋,去获取这把锁,如果获取不到就会继续自旋。但是它并不会去代替阻塞,虽然它减少了切换线程的开销,但是它占用了CPU的处理时间,如果持有锁的线程释放了锁的时候,自旋的线程自选的次数比较少的情况下是非常划算的。但是如果持有锁的线程持有锁时间比较长,让自旋的线程自旋次数比较多的话就会得不偿失。

    举一个有味道的栗子:你想上厕所,但是已经有人了。你可以先去其他的厕所看看有人没,也可以选择在厕所门口等着。前者就可以理解为阻塞,后者可以理解为自旋。如果等的时间短了,那还是比较好的,如果等的时间太长,那就。。。你懂得。

    所以,我们需要给自旋加一个限定次数,不能让它这样一直的这样转下去。如果超过了这个次数还没获得就要被挂起。这一切看似很美好,但是很有可能会出现一种问题,就是我们刚刚被挂起的时候占用锁的线程刚好释放了锁(这就是特别的缘分),那这样就非常尴尬了。所以在JDK6的时候有了自适应自旋锁。

自适应自旋锁

    那么什么是自适应自旋锁呢?就是把以前定死的次数给变活了,让自旋的次数不再是固定的了,而是根据上一次在同一个锁的自旋时间和锁的拥有者来判断。

    线程如果自旋成功了,那么下一次自旋的次数会更多,因为我们会认为,既然上次已经等待成功了,那么这次的自旋也很有可能成功,于是会增加自选的次数

    线程如果自旋失败了,那么下次这个锁就会对这个锁的自旋次数会减少,如果很多线程获取这个锁都自旋失败了很可能导致线程在下一次可能根本就不会去自旋,直接跳过。以免浪费CPU的处理时间。

锁消除

    什么时候会有多个线程去竞争一把锁的可能呢?当然是,这个加锁实例被创建在堆里的时候,因为堆是线程共享的,那么当这个锁实例在栈里被创建,那么还会有竞争锁的条件吗?当然没有了,因为栈是线程私有的,我这个根本就不具有竞争锁的条件,为什么还要去浪费时间加锁呢?但是有时候我们在用某些API的时候,他们是被加锁的,所以锁消除就是为了优化这种情况。

    它是基于逃逸分析而产生的一种优化手段。

锁粗化

    众所周知,在使用同步锁的时候,锁的范围越小越好。这样的话同步的数据就会越少,哪怕存在竞争,也会很快的去释放锁。但是吧,还是会有例外存在的。如果出现了一连串的加锁解锁操作,那就会对性能有一个比较大的消耗。所以锁粗化就产生了。

    什么是锁粗化呢?当出现多个连续的加锁解锁的时候,就会把锁的范围扩大,形成一个范围更大的锁。

锁升级

    大家都清楚MarkWord中锁的四种状态,分别是无锁/偏向锁、轻量级锁、重量级锁。这个顺序随着竞争激烈程度的加大会逐步升级。但是这只能升级不能降级。

    在MarkWord中,偏向锁有一个标志位,当使用偏向锁的时候,在MarkWord中偏向锁的标志位会标为1。如果没使用偏向锁就会标为0.

    现在JVM提供了三种监视器,偏向锁,轻量级锁,重量级锁。为了优化同步的运行机制,有了升级与降级,当JVM检测到不同的竞争状态时采取合适的锁的实现。

    当没有竞争的时候,默认使用偏向锁,JVM利用CAS操作,在对象头的MarkWord设置了线程ID,表示这个对象偏向于当前线程,但是这并不真正的涉及到互斥锁。一般用偏向锁也可以无竞争的开销。

    如果有另外的线程试着锁定某个被偏向过得对象,JVM首先会撤销偏向锁。然后切换到轻量级锁的实现,通过CAS修改对象头的MarkWord来试图获取锁,如果重试成功,就使用普通的轻量级锁,如果重试不成功,就会膨化为重量级锁。