Synchronized底层

Synchronized底层如何实现

相关的CSDN博客:

Synchronized关键字

synchronized俗称对象锁,它采用互斥的方式让同一时刻至多只有一个线程能够持有对象锁,其他线程再想获取这个对象锁就会阻塞住,这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切
换所打断

Synchronized有三种使用方法:

  1. 修饰实例方法,作用相当于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁。
  2. 修饰静态方法,相当于在当前类对象加锁,进入同步代码前要获得当前类对象实例的锁。
  3. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库的时候要获得给定对象的锁。
       image-20210328171234158

synchronized关键字的实现

synchronized不论是修饰代码块还是修饰方法都是通过持有对象锁来实现同步的。而这个对象的markword就指向了一个Monitor(锁/监视器)

1、java对象头的markword结构:

对象大致可以分为三个部分,分别是对象头,实例变量和填充字节,对象头分成两个部分:mark word和 klass word

图片说明

锁的类型和状态在对象头Mark Word中都有记录。在申请锁、锁升级等过程中JVM都需要读取对象的Mark Word数据。对于重量级锁对象的markword包含两个部分:指向重量级锁的指针和标志位

由此看来,monitor锁对象地址存在于每个Java对象的对象头中

2、Monitor结构:

每一个锁都对应一个monitor对象,在HotSpot虚拟机中它是由ObjectMonitor实现的(C++实现)

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
}

3、synchronized底层原理 = java对象头markword + 操作系统对象monitor:

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针
图片说明

  1. ​ synchronized无论是加在同步代码块还是方法上,效果都是加在对象上,其原理都是对一个对象上锁
  2. ​ 如何给这个obj上锁呢?当一个线程Thread-1要执行临界区的代码时,首先会通过obj对象的markword指向一个monitor锁对象
  3. ​ 当Thread-1线程持有monitor对象后,就会把monitor中的owner变量设置为当前线程Thread-1,同时计数器count+1表示当前对象锁被一个线程获取。
  4. ​ 当另一个线程Thread-2想要执行临界区的代码时,要判断monitor对象的属性Owner是否为null,如果为null,Thread-2线程就持有了对象锁可以执行临界区的代码,如果不为null,Thread-2线程就会放入monitor的EntryList阻塞队列中,处于阻塞状态Blocked。
  5. ​ 当Thread-0将临界区的代码执行完毕,将释放monitor(锁)并将owner变量置为null,同时计算器count-1,并通知EntryList阻塞队列中的线程,唤醒里面的线程

Synchronized 上锁原理

实现原理: JVM 是通过进入、退出 对象监视器(Monitor) 来实现对方法、同步块的同步的,而对象监视器的本质依赖于底层操作系统的 互斥锁(Mutex Lock) 实现。

具体实现是在编译之后在同步方法调用前加入一个monitor.enter指令,在退出方法和异常处插入monitor.exit的指令。

对于没有获取到锁的线程将会阻塞到方法入口处,直到获取锁的线程monitor.exit之后才能尝试继续获取锁。

流程图如下:

image-20210328171358568

monitorenter 指令:

​ 每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

​ 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者

​ 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.(可重入锁的原因)

​ 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit指令:

​ 执行monitorexit的线程必须是持有obj锁对象的线程

​ 指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程释放monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

​ Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出IllegalMonitorStateException的异常的原因。

使用javap -c Synchronize可以查看编译之后的具体信息。

image-20210328171429805

可以看到在同步块的入口和出口分别有monitorentermonitorexit指令。当执行monitorenter指令时,线程试图获取锁也就是获取monitor(monitor对象存在于每个Java对象的对象头中,synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因)的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

#Java##学习路径#
全部评论
整理的不错
1 回复 分享
发布于 2021-04-30 13:17
老哥,强
点赞 回复 分享
发布于 2021-04-25 19:13
强啊
点赞 回复 分享
发布于 2021-04-26 19:15

相关推荐

2024-12-13 18:24
浙江大学 Java
 面试时间: 12月13号,线上 Zoom 会议(45分钟)面试内容:1. 自我介绍 好好准备了英文版自我介绍,结果没有用上…2. 谈实习项目    * 项目中的定时任务是怎么实现的?我说自己没有负责定时任务,面试官接着问怎么实现定时操作,我提到了使用 SpringTask,感觉回答得不好。    * 项目是什么样的项目?    * 项目里面主要负责了什么功能?    * 项目里面怎么实现用户登录?    * 具体说一下怎么认证 token?3. 多线程方面    * 简单说一下 Java 怎么实现多线程。    * 如何开启一个线程?    * 线程池用过吗?有什么好处?    * volatile 关键字有什么用?    * 说一下 synchronized 关键字。    * ThreadLocal 怎么用,有什么作用?(我一直把 Thread 和 ThreadLocal 混淆了…面试官就开始拷问我 ThreadLocal 怎么用…)    * Java 的原子性了解吗?4. 基础问题    * 数据库用过吗?MySQL 有哪些索引?    * Spring Boot 常用的注解?    * 说一下 Spring 里面的 IOC。    * Spring 里面的注解有什么功能?    * 说一下怎么创建注解(脑子短路了,只说了加上相应注解,再用切面类,没说清楚)。    * 说一下你了解的设计模式。    * 说一下代理模式。    * 动态代理和静态代理怎么实现?    * JVM 垃圾回收机制使用的算法(完蛋,我不会…)5. 其他问题    * 用英文介绍自己的班级(很突然,我就巴拉巴拉说了一堆,乱七八糟)。    * 发一下你研究生一学期的成绩单(还没有期末考试,没必要看)。    * 在哪个校区上学?反问:* 面试官对我的面试表现有什么建议?(我感觉自己表现得不好,都不敢问到岗相关的问题)。* 面试官说要多重视基础,拿 JVM 垃圾回收机制使用的算法举例,强调原理机制也很重要。面试结果:* 面试官让我等后续消息,估计还有其他的候选人,需要横向对比。感受:* 出乎意料的是,面试中问了很多多线程相关的问题,数据库的部分问得不多。我觉得自己没有好好准备多线程,回答得不太理想。感觉希望不大,我还是继续投简历吧。总结:* 还是需要好好准备,继续恶补知识,多多刷面试题。#26届日常实习##日常实习面试##面试##ai智能作图#
查看27道真题和解析 ai智能作图
点赞 评论 收藏
分享
牛客2024-10-18投的,不久后会发笔试链接,笔试内容有选择题 多选题 还有算法题和数据结构设计题我记得。11-13 一面拷打我的弱鸡秒杀项目,被打烂了,我题都没听懂,大脑自动删除了这段记忆(依稀记得和库存有关);一些八股,时间有点久了,好像问了threadlocal;sql题:店铺商品金额查询每个商品的金额都大于500的店铺名称我的答案:SELECT DISTINCT 店铺 FROM 店铺商品表t1 WHERE NOT EXISTS (SELECT 1FROM 店铺商品表t2WHERE t1.店铺=t2.店铺 AND t2.金额<=500); 后面复盘感觉时间复杂度偏高,可以使用group by和having的;算法:给两个整数数组nums1和 nums2,返回两个数组中公共的、长度最长的连续的子数组的长度。示例1:输入:nums1 =[1,2,3,2,1],nums2= [3,2,1,4,7]输出: 3解释:长度最长的公共子数组是[3,2,1]。示例2:输入:nums1 = [0,0,0,0,0], nums2= [0,0,0,0,0]输出: 5我的答案:class Solution {    public int findLength(int[] nums1, int[] nums2) {        int m = nums1.length;        int n = nums2.length;        // dp[i][j] 表示以nums1[i-1]和nums2[j-1]结尾的最长公共子数组长度        int[][] dp = new int[m + 1][n + 1];        int maxLen = 0;                // 初始化第一行和第一列都是0,不用显式初始化,Java数组默认值就是0                // 填充dp数组        for (int i = 1; i             for (int j = 1; j                 if (nums1[i-1] == nums2[j-1]) {                    dp[i][j] = dp[i-1][j-1] + 1;                    maxLen = Math.max(maxLen, dp[i][j]);                }                // 如果当前元素不相等,那么以这两个元素结尾的公共子数组长度为0                // 不用显式设置,因为默认值就是0            }        }                return maxLen;    }}11-15 二面,应该是主管面问我研究方向,研究背景,我的模型什么的,研一的三篇paper;继续拷打我的弱鸡秒杀项目(QwQ);八股:mvcc;算法题:不重复子串问题。11-29 三面,CEO面12-3 好像是中午人力聊了一会(口头offer),下午发的正式offerps:字节挂了痛苦死我了#日常实习后端# #面经#
查看7道真题和解析
点赞 评论 收藏
分享
评论
5
32
分享
牛客网
牛客企业服务