草庐IT

再有人说synchronized是重量级锁,就把这篇文章扔给他看

Kyriez7 2023-10-07 原文

synchronized作为Java程序员最常用同步工具,很多人却对它的用法和实现原理一知半解,以至于还有不少人认为synchronized是重量级锁,性能较差,尽量少用。

但不可否认的是synchronized依然是并发首选工具,连volatile、CAS、ReentrantLock都无法动摇synchronized的地位。synchronized是工作面试中的必备技能,今天就跟着一灯一块深入剖析synchronized底层到底做了哪些优化?

synchronized是用来加锁的,而锁是加在对象上面,所以需要先聊一下JVM中对象构成。

1. 对象的构成

Java对象在JVM内存中由三块区域组成:对象头、实例数据和对齐填充。

对象头又分为:Mark Word(标记字段)、Class Pointer(类型指针)数组长度(如果是数组)。

实例数据是对象实际有效信息,包括本类信息和父类信息等。

对齐填充没有特殊含义,由于虚拟机要求 对象起始地址必须是8字节的整数倍,作用仅是字节对齐。

Class Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

重点关注一下对象头中Mark Word,里面存储了对象的hashcode、锁状态标识、持有锁的线程id、GC分代年龄等。

在32为的虚拟机中,Mark Word的组成如下:

image.png

2. synchronized锁优化

从JDK1.6开始,就对synchronized的实现机制进行了较大调整,包括使用JDK1.5引进的CAS自旋之外,还增加了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁等优化策略。由于使得synchronized性能极大提高,同时语义清晰、操作简单、无需手动关闭,所以推荐在允许的情况下尽量使用此关键字,同时在性能上此关键字还有优化的空间。

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,性能依次是从高到低。锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级

JDK 1.6 中默认是开启偏向锁和轻量级锁的,可以通过-XX:-UseBiasedLocking来禁用偏向锁。

2.1 自旋锁

线程的挂起与恢复需要CPU从用户态转为内核态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的

自旋锁就是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了CPU处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,这样反而会带来性能上的浪费。所以说,自旋等待的时间(自旋的次数)必须要有一个限度,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起。

自旋锁在JDK 1.4.2中引入,默认关闭,但是可以使用-XX:+UseSpinning开开启,在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整。

2.2 自适应自旋锁

JDK 1.6引入了更加智能的自旋锁,即自适应自旋锁自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。那它如何进行适应性自旋呢?

线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费CPU资源。

有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测会越来越准确,虚拟机会变得越来越聪明。

2.3 锁消除

JVM在JIT编译时通过对运行上下文的扫描,经过逃逸分析,对于某段代码不存在竞争或共享的可能性,就会讲这段代码的锁消除,提升程序运行效率。

public void method() {
    final Object LOCK = new Object();
    synchronized (LOCK) {
        // do something
    }
}

比如上面代码中锁,是方法中私有的,又是不可变的,完全没必要加锁,所以JVM就会执行锁消除

2.4 锁粗化

按理来说,同步块的作用范围应该尽可能小,仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,缩短阻塞时间,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。
但是加锁解锁也需要消耗资源,如果存在一系列的连续加锁解锁操作,可能会导致不必要的性能损耗。

锁粗化就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁,避免频繁的加锁解锁操作。

public void method(Object LOCK) {
    synchronized (LOCK) {
        // do something1
    }
    synchronized (LOCK) {
        // do something2
    }
}

比如上面方法中两个加锁的代码块,完全可以合并成一个,减少频繁加锁解锁带来的开销,提升程序运行效率。

2.5 偏向锁

为什么要引入偏向锁?

因为经过HotSpot的作者大量的研究发现,大多数时候是不存在锁竞争的,通常是一个线程多次获得同一把锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

2.6 轻量级锁

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的场景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋(CAS)这等待锁释放。

加锁过程:当代码进入同步块时,如果同步对象为无锁状态时,当前线程会在栈帧中创建一个锁记录(Lock Record)区域,同时将锁对象的对象头中 Mark Word 拷贝到锁记录中,再尝试使用 CASMark Word 更新为指向锁记录的指针。如果更新成功,当前线程就获得了锁。

解锁过程:轻量锁的解锁过程也是利用 CAS 来实现的,会尝试锁记录替换回锁对象的 Mark Word 。如果替换成功则说明整个同步操作完成,失败则说明有其他线程尝试获取锁,这时就会唤醒被挂起的线程(此时已经膨胀为重量锁)

2.7 重量级锁

synchronized是通过对象内部的监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的互斥锁(Mutex Lock)来实现的。

重量级锁的工作流程:当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长,所以重量级锁的开销还是很大的。

在锁竞争激烈、锁持有时间长的场景,还是适合使用重量级锁的。

2.8 锁升级过程

image.png

2.9 锁的优缺点对比

锁的性能从低到高,依次是无锁、偏向锁、轻量级锁、重量级锁。不同的锁只是适合不同的场景,大家可以依据实际场景自行选择。

image.png

3. 总结

synchronized锁经过多次迭代优化,已经不像以前那么重了,在JDK1.8的ConcurrentHashMap源码中已经大量使用synchronized做同步控制,大家在日常开发中可以放心使用了。

有关再有人说synchronized是重量级锁,就把这篇文章扔给他看的更多相关文章

  1. ruby-on-rails - rails delete_if 使用哈希忽略当前文章(中间人) - 2

    我为你们准备了一个简单的。我想要一个特色内容部分,其中排除了当前文章所以这可以通过delete_if使用MiddlemanBlog:但是我使用的是中间人代理,所以我无法访问current_article方法...我有一个YAML结构,其中包含以下模拟数据(以及其他数据),文件夹设置如下:data>site>caseStudy>RANDOM-ID423536.yaml(由CMS生成)在每个yaml文件中,您会发现如下内容::id:2k1YccJrQsKE2siSO6o6ac:title:Heyplace我的config.rb看起来像这样data.site.caseStudy.eachdo

  2. ruby - 在 Middleman 中移动博客文章位置 - 2

    我正在为我的网站使用MiddlemanBloggem,但默认情况下,博客文章似乎需要位于/source中,这在查看vim中的树时并不是特别好并尝试在其中找到其他文件之一(例如模板)。通过查看文档,我看不出是否有任何方法可以移动博客文章,以便将它们存储在其他地方,例如blog_articles文件夹或类似文件夹。这可能吗? 最佳答案 将以下内容放入您的config.rb文件中。activate:blogdo|blog|blog.permalink=":year-:month-:day-:title.html"blog.sources=

  3. ruby-on-rails - 文章#index 中的 Ruby on Rails 教程 NoMethodError - 2

    所以我正在关注http://guides.rubyonrails.org/getting_started.html上的官方ROR教程我被困在第5.8节,它教我如何列出所有文章下面是我的controller和index.html.erbControllerclassArticlesControllerindex.html.erbListingarticlesTitleText我收到带有错误消息的NoMethodErrorinArticles#indexundefinedmethod`each'fornil:NilClass"怎么了?我从网站上复制并粘贴了代码以查看我做错了什么,但仍然无法

  4. ruby - 为什么 Ruby 人说他们不需要接口(interface)? - 2

    Ruby是否与其他OOP语言(例如:PHP)不同,使接口(interface)变得无用?它有某种替代品吗?编辑:一些说明:在其他语言中(例如:PHP),您并不“需要”接口(interface)(它们在代码级别不是强制性的)。你用它们来订立契约(Contract),改进软件的架构。因此,'在ruby​​中你不需要接口(interface)/在其他语言中你需要接口(interface)因为XXX'的断言是错误的。不,混合不是接口(interface),它们是完全不同的东西(PHP5.4实现了混合)。你用过接口(interface)吗?是的,PHP是OOP。语言不断发展,欢迎来到现在。

  5. ruby - 如何在 Capybara 中使用 synchronize? - 2

    如果如何使用wait_until非常清楚(我在通过nativeWebdriver方法创建测试时使用过这样的方法),但不是新的同步方法(抱歉:))。我已经阅读了关于为什么不推荐使用wait_until的主题,我已经阅读了相关文章,我已经阅读了带有方法描述的文档,还阅读了描述中的代码。但我没有找到任何示例或教程如何使用此方法。任何人,请提供一些我(也许还有其他人)可以看到并学习如何使用此方法的案例例如案例expect(actual).toequal(expected)我应该在哪里“放置”同步方法以仅在超时后才获得否定异常?UPD:有兴趣的请查看此链接:http://www.elabs.se

  6. ruby-on-rails - 文章中的 ActionController::UrlGenerationError#edit - 2

    我收到以下错误:没有路由匹配{:action=>"show",:controller=>"articles",:id=>nil}缺少必需的键:[:id]以下是显示错误的代码。这是什么错误,每当我从上一个屏幕点击编辑时,我想我正在发送文章ID。这是我的rake路由输出PrefixVerbURIPatternController#Actionwelcome_indexGET/welcome/index(.:format)welcome#indexarticlesGET/articles(.:format)articles#indexPOST/articles(.:format)articl

  7. ruby-on-rails - Rails 5 上一篇或下一篇文章仅来自特定标签 - 2

    我有一个名为posts的资源,其中有很多。但是,每个帖子可以有多个标签。我希望用户只能从所选标签转到上一篇和下一篇文章。我让它适用于上一个下一个数据库中的所有帖子,但是当我单击一个标签并显示所有标签时,上一个/下一个不符合标签是什么。如果我访问与routes.rb中定义的代码关联的url,get'tags/:tag',to:'posts#index',as::tag,它会列出索引中的所有标签。我不想要这个,我希望用户能够单击上一个或下一个,并且只能在与标签关联的帖子上执行此操作。注意:我使用的是friendly_idgemcontrollers/posts_controller.rbd

  8. ruby - ruby 是否具有与 synchronize 关键字等效的 Java? - 2

    ruby是否有Java中的synchronize关键字?我使用的是1.9.1,但我不太明白执行此操作的优雅方式。 最佳答案 它没有synchronize关键字,但您可以通过Monitor类获得非常相似的东西。以下是ProgrammingRuby1.8一书中的示例:require'monitor'classCounter 关于ruby-ruby是否具有与synchronize关键字等效的Java?,我们在StackOverflow上找到一个类似的问题: http

  9. ruby - Ruby 中用于并发的 Synchronized 方法 - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:DoesrubyhavetheJavaequivalentofsynchronizekeyword?在Java中,我们可以通过在函数定义中使用“synchronized”关键字来使方法“同步”。我们如何在Ruby中做到这一点?

  10. ruby-on-rails - Rails 文章助手 - "a"或 "an" - 2

    有谁知道RailsHelper可以自动将适当的文章添加到给定字符串的前面?例如,如果我将“apple”传递给函数,它会变成“anapple”,而如果我要发送“banana”,它会返回“abanana”我已经检查了RailsTextHelpermodule但找不到任何东西。如果这是重复的,我们深表歉意,但诚然,这是一个很难搜索的答案...... 最佳答案 据我所知没有,但为此编写一个帮助程序似乎很简单,对吗?离开我的头顶defindefinite_articlerize(params_word)%w(aeiou).include?(p

随机推荐