前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[转]Java Synchronised机制

[转]Java Synchronised机制

作者头像
DH镔
发布2019-12-20 16:55:59
4260
发布2019-12-20 16:55:59
举报
文章被收录于专栏:编程从踩坑到跳坑

Java中锁的控制可以参看这篇文章: Java多线程抢占

I. 原末

矛盾1

A: 重量级锁中的阻塞(挂起线程/恢复线程): 需要转入内核态中完成,有很大的性能影响。

B: 锁大多数情况都是在很短的时间执行完成。

解决方案: 引入轻量锁(通过自旋来完成锁竞争)。

矛盾2

A: 轻量级锁中的自旋: 占用CPU时间,增加CPU的消耗(因此在多核处理器上优势更明显)。

B: 如果某锁始终是被长期占用,导致自旋如果没有把握好,白白浪费CPU资源。

解决方案: JDK5中引入默认自旋次数为10(用户可以通过-XX:PreBlockSpin进行修改), JDK6中更是引入了自适应自旋(简单来说如果自旋成功概率高,就会允许等待更长的时间(如100次自旋),如果失败率很高,那很有可能就不做自旋,直接升级为重量级锁,实际场景中,HotSpot认为最佳时间应该是一个线程上下文切换的时间,而是否自旋以及自旋次数更是与对CPUs的负载、CPUs是否处于节电模式等息息相关的)。

矛盾3

A: 无论是轻量级锁还是重量级锁: 在进入与退出时都要通过CAS修改对象头中的Mark Word来进行加锁与释放锁。

B: 在一些情况下总是同一线程多次获得锁,此时第二次再重新做CAS修改对象头中的Mark Word这样的操作,有些多余。

解决方案: JDK6引入偏向锁(首次需要通过CAS修改对象头中的Mark Word,之后该线程再进入只需要比较对象头中的Mark Word的Thread ID是否与当前的一致,如果一致说明已经取得锁,就不用再CAS了)。

矛盾4

A: 项目中代码块中可能绝大情况下都是多线程访问。

B: 每次都是先偏向锁然后过渡到轻量锁,而偏向锁能用到的又很少。

解决方案: 可以使用-XX:-UseBiasedLocking=false禁用偏向锁。

矛盾5

A: 代码中JDK原生或其他的工具方法中带有大量的加锁。

B: 实际过程中,很有可能很多加锁是无效的(如局部变量作为锁,由于每次都是新对象新锁,所以没有意义)。

解决方法: 引入锁削除(虚拟机即时编译器(JIT)运行时,依据逃逸分析的数据检测到不可能存在竞争的锁,就自动将该锁消除)。

矛盾6

A: 为了让锁颗粒度更小,或者原生方法中带有锁,很有可能在一个频繁执行(如循环)中对同一对象加锁。

B: 由于在频繁的执行中,反复的加锁和解锁,这种频繁的锁竞争带来很大的性能损耗。

解决方法: 引入锁膨胀(会自动将锁的范围拓展到操作序列(如循环)外, 可以理解为将一些反复的锁合为一个锁放在它们外部)。

II. 基本原理

JVM会为每个对象分配一个monitor,而同时只能有一个线程可以获得该对象monitor的所有权。在线程进入时通过monitorenter尝试取得对象monitor所有权,退出时通过monitorexit释放对象monitor所有权。

monitorentermonitorexit在编译后对称插入代码。

  • monitorenter: 被插入到同步代码块之前。
  • monitorexit: 被插到同步代码块之后或异常处。

1. 相关数据存在哪里?

对象头。

对象头结构

数组会多1字宽(32位: 4字节)来存储数组长度

长度

内容

说明

1字宽

Mark Word

存储对象的hashCode或锁信息等

1字宽

Class Metadata Address

存储对象类型数据的指针

1字宽

Array length

数组长度(如果是数组对象)

而对象的锁,一般只和Mark Word有关。

2. 各个锁的关系以及升级情况?

锁升级是单向的: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

III. 多线程下数据同步

这类锁/关键字主要是为了维护数据在高并发情况下的一致性/稳定性。

1. 数据库中的锁

共享锁(Share Lock)

又称为读锁

多个线程可并发的获得某个数据的共享锁锁,并行读取数据。在数据存在共享锁期间,不能修改数据,不能加排他锁。

如MySQL中,在查询语句最后加上LOCK IN SHARE MODE

排他锁(eXclusive Lock)

又称为写锁

同能只能有一个线程可以获得某个数据的排他锁。在线程获取排他锁后,该线程可对数据读写,但是其他线程不能对该数据添加任何锁。

2. volatile

如果一个共享变量被声明成volatile,java线程内存模型将会确保所有线程看到这个变量的值是一致的。

  • 基本策略: 写操作时,会有Lock前缀指定,处理器会立马将修改直接写回系统内存,并且其他处理器会将该值在其上的高速缓存标为无效。
  • 可能带来的性能消耗: 写操作实时写回内存,锁总线/锁内存。
  • 优势: 一些场景上相比synchronized,执行成本更低(不会引起线程上下文切换以及调度),使用更方便。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • I. 原末
    • 矛盾1
      • 矛盾2
        • 矛盾3
          • 矛盾4
            • 矛盾5
              • 矛盾6
              • II. 基本原理
                • 1. 相关数据存在哪里?
                  • 对象头结构
                • 2. 各个锁的关系以及升级情况?
                • III. 多线程下数据同步
                  • 1. 数据库中的锁
                    • 共享锁(Share Lock)
                    • 排他锁(eXclusive Lock)
                  • 2. volatile
                  相关产品与服务
                  对象存储
                  对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档