并发第三弹 synchronized锁优化详解

大家都知道,Java中提供了一个synchronized关键字来修饰同步块,在JDK6之前synchronized的性能是十分低的,而JDK6针对synchronized做了很多优化


synchronized底层原理


synchronized关键字编译后会在同步块前后分别形成monitorenter和monitorexit两个字节码指令。


编译前

public class ThreadCommon{
    public void run(){
        synchronized (this){
            System.out.println("hello");
        }
    }
}


编译后

并发第三弹 synchronized锁优化详解


每个Java对象都有一个对象头,而对象头里面有一个指针,指向一个monitor对象,monitor可以理解为对象监视器,monitor对象由C++实现,底层依赖的是操作系统的互斥量(mutex)。


执行monitorenter指令时,首先尝试获取锁,如果这个对象没有被锁定,则当前线程将持有锁,monitor计数器会加1,否则当前线程阻塞。

执行monitorexit指令时,monitor计数器会减1,如果减为0则代表锁被释放。

锁是一个重量级的操作,因为线程的阻塞或唤醒都是由操作系统来控制的,无法人为干预,而由操作系统来控制就不可避免陷入到用户态和内核态的转换当中,甚至转换时间大于用户代码的执行时间(比如a++这种简单的代码),下面就来介绍synchronized的锁优化机制。


锁优化


先来看看有哪些锁优化的技术:

  • 自适应自旋锁

  • 锁消除

  • 锁粗化

  • 轻量级锁

  • 偏向锁

下面一一讲解。


自旋

普通自旋:

  • 循环获取锁,虽然不会导致当前线程阻塞,但是会一直消耗处理器资源。

固定次数的自旋锁:

  • 比如固定10次,10次自旋后仍没有获取到锁就会放弃;

自适应自旋锁:

  • 自旋次数不固定,而由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的;


锁消除

锁消除是指虚拟机即时编译器在运行时,对一些代码要求同步,但是对被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须再进行。


锁粗化

如果有一连串的操作(比如循环),每个子操作用synchronized同步块包围,那么这一连串操作实际上一直在加锁->释放锁->…->加锁->释放锁,频繁的加锁释放锁会影响性能,此时虚拟机就会做出锁粗化的优化,直接对这一连串的操作整体加锁,而子操作则不加锁。


轻量级锁

在讲轻量级锁之前,有必要回顾一下对象的内存布局之对象头之Mark Word部分的存储内容,HotSpot虚拟机对象三部曲


不同锁状态下的Mark Word如下图所示。

并发第三弹 synchronized锁优化详解

在 HotSpot虚拟机对象三部曲 这篇文章中,我已经介绍了未锁定和轻量级锁加锁的情况,下面对轻量级锁进行一些补充。


轻量级锁解锁过程:

  1. 轻量级锁通过CAS解锁,如果对象的Mark Word仍然指向当前线程的锁记录,那就用CAS将对象的Mark Word与线程中的Displaced Mark Word相互替换;

  2. 如果替换成功,则解锁成功;

  3. 如果替换失败,则说明有其他线程尝试获取该锁,需要在释放锁的同时,唤醒被挂起的线程;


锁膨胀:

  • 如果出现两条及以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁,此时Mark Word存储指向重量级锁(monitor对象,互斥量)的指针,后面等待锁的线程也必须进入阻塞状态;


轻量级锁能提升程序同步性能是因为对于绝大部分的锁,在整个同步周期内都不存在竞争。如果没有竞争,轻量级锁通过CAS避免了使用互斥量的开销;但如果确实存在锁竞争,除了互斥量的本身开销外,还额外发生了CAS的开销。因此在有竞争的情况下,轻量级锁反而会比传统的重量级锁更慢。



偏向锁

偏是偏心的意思,表示这个锁会偏向于第一个获得它的线程,如果该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步

轻量级锁是在无竞争的情况下用CAS去消除同步使用的互斥量,而偏向锁则是在无竞争的情况下直接消除整个同步,连CAS也不做了。


偏向过程:

  1. 假设虚拟机启用了偏向锁,当锁对象第一次被线程获取的时候,则进入偏向模式,同时使用CAS将持有偏向锁的线程ID记录在对象的Mark Word之中;

  2. 如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作(例如加锁、解锁及对Mark Word的更新操作等);

  3. 一旦出现另外一个线程去尝试获取这个锁的情况,则偏向模式结束,并且进入锁定状态(持有偏向锁的线程未进入同步块)或轻量级锁定状态(持有偏向锁的线程已进入同步块);


并发第三弹 synchronized锁优化详解




原文始发于微信公众号(初心JAVA):并发第三弹 synchronized锁优化详解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/35551.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!