当前位置: 首页 > article >正文

Java中锁的深入理解

目录

对象头的理解

Monitor(锁)

锁类型

偏向锁

偏向锁的优化机制

轻量级锁

重量级锁


对象头的理解

在32位Java虚拟机中普通对象的对象头是占用8个字节,其中4个字节为Mark Word。用来存储对象的哈希值,对象创建后在JVM中的生命(经历GC回收后存活次数)等信息。另外四个字节为Klass Word用来存储对象类型,是String还是Student又或是Teacher。

Mark Word的存储结构为

Monitor(锁)

Monitor锁结构如下

这三个区域分别代表

  1. WaitSet:线程通过wait()方法进入的阻塞状态,从Owner进入WaitSet集合,通过notify()方法唤醒后进入EntryList。
  2. EntryList:阻塞线程集合,需要通过非公平竞争去获取锁
  3. Owner:存储当前获取锁的线程。

当一个对象被加入重量级锁时,Mark Word会存储Monitor的地址。

锁类型

均针对于synchronized来进行辨析

偏向锁

偏向锁CPU损耗是最小的一种,在这种锁状态下,系统会认为这个锁通常只会由一个线程独占。默认情况下是开启的,对于本就是需要多线程争抢的对象来说,可以选择禁用偏向锁。

轻量级锁每次都会进行CAS操作,这也会消耗CPU资源,因此,偏向锁采用的是对象中的Mark Word存储的是线程ID。每次加锁时,先判断对象中的Mark Word是否是本线程ID(轻量级锁和偏向锁的锁对象Mark Word存储的信息不同,轻量级锁存储的是对象的hash值,偏向锁存储的是ThreadID)。

当对象被加入偏向锁后,记录的ThreadID并不会因为解锁而消失,而是继续存储在对象的Mark Word中。如果其他线程获取该对象加锁,会撤销ThreadID,变成轻量级锁

当一个可偏向对象调用hashCode方法后,会关闭偏向锁,用来对象的Mark Word用来存储该对象的hash值。

偏向锁的优化机制

批量重偏向:当可偏向对象因为被多个线程访问导致的撤销偏向锁次数过多时(默认值为20),后续的可偏向对象就不会再次进行撤销偏向锁了,因为撤销偏向锁也是需要CPU资源的。而是进行重偏向,JVM会认为初始偏向时,偏向错了,将后续的可偏向对象中的Mark Word修改为现在正在执行的ThreadID,已经被撤销偏向锁的对象则无法恢复。

批量撤销:当偏向锁被撤销次数过多时(默认为40次),JVM会认为,就不应该开启偏向锁,此后创建的每一个对象都是不可偏向的。

轻量级锁

在这种锁状态下,通常应用是一个对象虽然会被多个线程访问,但是多个线程的访问是错开时间的,并不存在竞争关系。

static final Object obj = new Object(); 
public static void method1(){
	synchronized(obj){
        method2();
    }
}

public static void method2(){
	synchronized(obj){
    }
}

首先这是一个线程执行,那么在执行method1时,栈帧中会创建一个Lock Record(锁记录)。里面存储对象的引用地址以及存储锁对象的Mark Word。

如下图所示

接着进行下一步,Object reference指向锁对象,并尝试通过cas交换锁对象的Mark Word。

如下图所示

如果cas成功,那么对象的对象头Mark Word存储的是锁记录的地址以及其状态,表示对该对象进行加锁

如果cas失败,则要分情况看待:

  • 一种是,该对象已经被其他线程上锁,这时说明该对象存在竞争情况,进入锁膨胀状态
  • 一种是,该对象已经被该线程上锁,这属于锁重入,再加入一条Lock Record作为重入计数。

对于第二种情况进行分析。在进行method1时,又进行了method2,此时,会cas失败。但是并不影响。

进行解锁时,取出的锁记录为null时,说明存在重入,此时重入计数-1。

如果取出的锁记录不为null,说明该线程已经不对该对象上锁了,进行真正的解锁操作,通过cas将对象的原有Mark Word恢复给该对象。如果成功的话说明解锁成功,如果cas失败说明进行了锁膨胀升级到了重量级锁,此时去执行重量级锁解锁流程。

重量级锁

最耗费CPU资源的加锁方式。

当线程0已经为对象加上了轻量级锁后,线程1再次对该对象加入轻量级锁,此时CAS失败,那么线程1会为该对象申请Monitor锁。然后自己进入Monitor锁中的EntryList

申请Monitor锁后,会将其地址存储在该对象的Mark Word中。

当线程0进行解锁操作时,会发现CAS解锁失败,此时会进入重量级解锁流程,即通过Monitor地址,找到Monitor锁后,将Owner设置为null,唤醒EntryList中的阻塞线程。


自旋优化:当线程加锁时发现锁被占用会进入阻塞状态,但是这样会大量进行线程上下文切换,比较占用CPU,因此可以使用自旋优化,所谓自旋优化,是线程循环获取锁,失败后还是要去获取锁,如果在规定次数内还没有获取到锁,那么进入阻塞状态,因为这是一个循环过程,也是要占用CPU资源的。注意:单核CPU没有自旋的必要。


http://www.kler.cn/a/135219.html

相关文章:

  • MySQ怎么使用语法介绍(详细)
  • aws(学习笔记第十二课) 使用AWS的RDS-MySQL
  • 【Qt】在 Qt Creator 中使用图片资源方法(含素材网站推荐)
  • Java基于SpringBoot+Vue的宠物共享平台的设计与实现(附源码,文档)
  • go reflect 反射
  • DHCP与DNS安全管理
  • CentOS Stream 9-使用 systemd 管理自己程序时自定义日志路径
  • Python大数据之linux学习总结——day11_ZooKeeper
  • Dubbo快速实践
  • Unity在Windows选项下没有Auto Streaming
  • virtuoso 后仿 ADE L error
  • Cannot find proj.db
  • 二十三种设计模式全面解析-职责链模式(Chain of Responsibility Pattern):解放代码责任链,提升灵活性与可维护性
  • 哪些软件可以监控电脑(保姆级教程!值得收藏!)
  • Vue3+Vite实现工程化,插值表达式和v-text以及v-html
  • java switch case 多条件 正确案例错误案例
  • 【ArcGIS Pro二次开发】:CC工具箱1.1.1更新_免费_安装即可用
  • 如何防止研发把代码上传到私人gitee/github?
  • 猫罐头哪个牌子质量好性价比高?推荐十款猫罐头品牌排行榜!
  • 用GPT 搭建一个占星术、解梦、塔罗牌占卜和命理学服务
  • 论文阅读:“基于特征检测与深度特征描述的点云粗对齐算法”
  • ModernCSS.dev - 来自微软前端工程师的 CSS 高级教程,讲解如何用新的 CSS 语法来解决旧的问题
  • memcacheredis构建缓存服务器
  • 矿区安全检查VR模拟仿真培训系统更全面、生动有效
  • Linux内核调试篇——获取内核函数地址的四种方法(一文解决)
  • 对象和数据结构