面试官,别挂电话,Synchronized,我还能说上半小时。

Synchronized关键字,经常被用于线程同步。执行Synchronized修饰的同步代码块的线程,首先会获得“对象的锁”,如果有其他线程尝试执行同步代码块,会阻塞,直到该线程执行完同步代码,释放“对象锁”。上面的概念,肯定不陌生,但是对象锁具体是什么,或许你不太清楚,本文从其底层原理出发,详细解读Synchronized关键字

Synchronized关键字概念

synchronized是Java中关键字,是利用锁机制来实现同步,锁机制有如下两种特性

  • 互斥性(也叫原子性):同一时刻只允许一个线程获取对象锁
  • 可见性:线程获取对象锁后,会清空线程工作内存中的同步代码块使用到的共享变量
    对象释放锁前,会将线程工作内存中对共享变量的修改刷新回主内存

对象锁与类锁

在Java中,每个对象都会有一个monitor对象,monitor对象其实就是经常说的,对象锁或者称为监视器锁。而类锁(类似使用Synchronized(Object.class)), 其实是通过对象锁实现,如果熟悉类加载机制,肯定知道,类加载的同时,会在堆中,new一个Class对象。此Class对象的monitor对象就是类锁,由此可知,每个类只有一个类锁

对象头

Java对象在内存中存储的布局可以分为三块区域: 对象头、实例数据、对齐填充。 对象头,分为两个部分,第一个部分存储对象自身的运行时数据,又称为Mark Word,32位虚拟机占32bit,64位虚拟机占64bit。如图所示,不同锁状态下,Mark Word的结构.第二部分存储类指针

对象锁原理(Monitor)

Java虚拟机中,monitor是由ObjectMonitor实现的,主要数据结构如下

1 ObjectMonitor() {2     _header       = NULL;//markOop对象头3     _count        = 0;4     _waiters      = 0,//等待线程数5     _recursions   = 0; //重入次数6     _object       = NULL;7     _owner        = NULL;//指向获得ObjectMonitor对象的线程8     _WaitSet      = NULL;//处于wait状态的线程,会被加入到wait set;9     _WaitSetLock  = 0 ;
10     _Responsible  = NULL ;
11     _succ         = NULL ;
12     _cxq          = NULL ;
13     FreeNext      = NULL ;
14     _EntryList    = NULL ;//处于等待锁block状态的线程,会被加入到entry set;
15     _SpinFreq     = 0 ;
16     _SpinClock    = 0 ;
17     OwnerIsThread = 0 ;// _owner is (Thread *) vs SP/BasicLock
18     _previous_owner_tid = 0;// 监视器前一个拥有者线程的ID
19   }

ObjectMonitor主要有几个需要关注的成员变量

  • _owner:指向获得ObjectMonitor对象的线程
  • _EntryList: 处于等待锁block状态的线程,会被加入到entry set
  • _WiatSet: 处于wait状态的线程,会被加入到wait set(调用同步对象wait方法)

多个线程同时访问一段同步代码时,首先会进入_EntryList集合,进行阻塞等待, 当线程获取到对象的monitor后进入owner区域,并把monitor中的_owner变量指向该线程,同时monitor中的计数器count自加一,若线程调用同步对象的wait()方法将释放当前持有的monitor,_owner变量重置为null,count自减一,同时该线程进入_WaitSet中等待唤醒,线程执行完同步代码块后,也将_Ownercount变量重置.

代码块加锁解锁过程

public class SyncCodeBlock{public int i;public void syncTask(){synchronized(this) {i++;}}
}

反编译后,得到上述代码的字节码

public void syncTask();Code:0: aload_01: dup2: astore_13: monitorenter4: aload_05: dup6: getfield      #2                  // Field i:I9: iconst_110: iadd11: putfield      #2                  // Field i:I14: aload_115: monitorexit16: goto          2419: astore_220: aload_121: monitorexit22: aload_223: athrow24: return

从字节码中可以看出,Synchronized底层是使用monitorenter,monitorexit指令实现线程同步.

  • 执行monitorenter指令时,线程将尝试获取同步对象的锁(即monitor对象),若monitor的count变量(记录线程进入次数)为0, 则将count设置为1,_owner设置为当前线程,``.取锁成功.如果线程已拥有该对象锁,则可以重入该锁.重入时count计算器的值也会加一
  • 指向monitorexit指令时,执行器将count计数器减一,当计数器为0时,其他线程将有机会持有

monitor.值得注意的是,编译器会确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都会执行相应的monitorexit指令.无论此方法是正常结束还是异常结束.

如果方法异常完成,编译器会自动产生一个异常处理器,异常处理器的目的就是用来执行monitorexit指令.从字节码中也可以看出多了一个monitorexit指令.它就是异常结束时被执行的释放monitor指令.

方法体加锁解锁过程

public class Main{public int i;public synchronized void syncTask(){i++;}
}

反编译上述代码后,得到如下字节码

public synchronized void syncTask();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field i:I5: iconst_16: iadd7: putfield      #2                  // Field i:I10: return

方法体的同步时隐式,即无需字节码指令来控制.JVM可以从方法常量池中的方法表结构(method_info structure)中的ACC_SYNCHRONIZED访问标志来区分一个方法是否同步方法.

当调用方法时,检测方法的`ACC_SYNCHRONIZED``是否被设置,如果设置了,线程需先持有该对象的monitor,然后执行方法,最后方法完成时释放monitor.

如果一个同步方法执行期间抛出了异常,并且方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放.

synchronized 异常_面试官,别挂电话,Synchronized,我还能说上半小时相关推荐

  1. redis怎么修改_面试官问我Redis事务,还问我有哪些实现方式

    ❝ 「第12期」 距离大叔的80期小目标还有68期,今天大叔要跟大家分享的内容是 -- Reids中的事务.同样,这也是redis中重要指数为四颗星的必备基础知识点.下面一起来了解一下吧. ❞ 相信大 ...

  2. 在c#中用mutex类实现线程的互斥_面试官经常问的synchronized实现原理和锁升级过程,你真的了解吗...

    本篇文章主要从字节码和JVM底层来分析synchronized实现原理和锁升级过程,其中涉及到了简单认识字节码.对象内部结构以及ObjectMonitor等知识点. 阅读本文之前,如果大家对synch ...

  3. 反序列化对象列表发生异常_面试官:你知道Java对象的序列化与反序列化背后的原理吗?...

    序列化与反序列化 序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程.一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等.在网络传输过程中,可以是字节或是 ...

  4. 惨遭面试官吊打高并发系统设计,回来学习 2400 小时后成功复仇

    惨遭面试官吊打高并发系统设计,回来学习 2400 小时后成功复仇原创 2021-06-22 06:35·Java 小菜去年的金九银十,我和大多数的同行一样加入了升职涨薪的潮水,我早在 2 个月前就开始 ...

  5. 面试官:不使用synchronized和lock,如何实现一个线程安全的单例?

    单例,大家肯定都不陌生,这是Java中很重要的一个设计模式.稍微了解一点单例的朋友也都知道实现单例是要考虑并发问题的,一般情况下,我们都会使用synchronized来保证线程安全. 那么,如果有这样 ...

  6. java resume过时方法_面试官没想到,一个 Java 线程生命周期,我可以扯半小时

    面试官:你不是精通 Java 并发吗?从基础的 Java 线程生命周期开始讲讲吧. 好的,面试官.吧啦啦啦... 如果要说 Java 线程的生命周期的话,那我觉得就要先说说操作系统的线程生命周期 因为 ...

  7. mysql怎么用_面试官都是这样发问的,连环冲锋炮,看你怎么抵挡(上)

    本内容来源于和尚 16 年毕业的学长,先在 58,后阿里,如今准备跳槽了,以下内容为他的最近面试经历 我最近从大厂离职之后在合肥呆了个把月,之前已经准备了半个多月,从7月底开始投简历面试,目前是jav ...

  8. redis主从复制如何保证数据一致性_面试官:Redis 主从复制时网络开小差了怎么整?...

    上周因为实在太忙就认认真真写了一篇水文,吹了一下自己过去的经历,反响竟然超出了我的预期,并且后台还有读者留言表示想看续集的.哈哈,果然大家还是对水文更有热情. 这期我们继续回到之前的 Redis 话题 ...

  9. .jar中没有主清单属性_面试官问:为什么SpringBoot的 jar 可以直接运行?

    点击上方蓝色字体,选择"设为星标" 优质文章,及时送达 来源 | https://urlify.cn/uQvIna SpringBoot提供了一个插件spring-boot-mav ...

最新文章

  1. 滚动联动-单独滚动与文档滚动
  2. DNS服务器的默认区域文件名,DNS服务器全攻略之三 :创建与管理DNS区域.doc
  3. zTree 优秀的jquery树插件
  4. c语言位运算八进制转二进制,C语言十进制转换二进制八,十六进制。二进制转十进制。...
  5. flutter安装教程(win7)
  6. PHP开源软件《个人管理系统》-希望大家一起来开发
  7. 为什么Dell官方声卡驱动安装不上的原因分析与解决?
  8. 三维全景虚拟现实:现在的VR全景行业怎么样?|时空克隆 三维视频融合 投影融合 点卯 魔镜系列
  9. oracle的floor用法,oracle ceil floor 函数的用法
  10. 微信大数据推荐算法比赛
  11. 产品经理如何进行市场分析的知识点
  12. 与技术无关,但却值得码农们好好读一读的怪书:禅与摩托车维修艺术
  13. README.generic-sessions.md
  14. 客户端时不时接收到10054错误
  15. 线索二叉树的前序遍历
  16. windows+php+ffmpeg转换音频格式
  17. imagesize()函数获取图片信息
  18. ThinkPad T14安装系统后,重启经常进不去系统,如何解决
  19. mitmproxy系列抓包工具使用与实战
  20. 菜刀 mysql_chopper菜刀一句话操作mysql数据库乱码问题脚本安全 -电脑资料

热门文章

  1. IDC:今年全球认知和人工智能系统支出将突破125亿美元
  2. SQL Server监控全解析
  3. EasyUI 搜索框
  4. iptable防火墙流程图
  5. swift版本hello
  6. 批量创建用户邮箱并修改别名
  7. mysql中有time吗_mysql中 datatime与timestamp的区别说明
  8. rtk采点后如何导入cad_ZEMAX:如何导入CAD物体
  9. C++自学24:唯一智能指针(make_unique/unique_ptr/reset/release/get/13.1)
  10. pythonjava有什么区别_Java与Python的区别对比