synchronized是什么

synchronized是java同步锁,同一时刻多个线程对同一资源进行修改时,能够保证同一时刻只有一个线程获取到资源并对其进行修改,因此保证了线程安全性。

synchronized可以修饰方法和代码块,底层实现的逻辑略有不同。

Object obj=new Object();
synchronized(obj){
//do soming
}

编译后的代码为:

 ...
10 astore_2
11 monitorenter
12 aload_2
13 monitorexit
14 goto 22 (+8)
17 astore_3
18 aload_2
19 monitorexit
20 aload_3
21 athrow
22 return

当代码执行到synchronize(obj)时,对应的字节码为monitorenter进行加锁操作,代码执行完后就是monitorexit进行锁的释放。两个 monitorexit是正常退出和异常退出两种情况下锁的释放。

public synchronized void test1(){
//do somthing
}

当修饰方法时是在编译后的字节码上加上了synchronized的访问标识

Monitor机制

Monitor是一种同步机制,它的作用是保证同一时刻只有一个线程能访问到受保护的资源,JVM中的同步是基于进入和退出监视对象来实现的,是synchronized的底层实现,每个对象实例都是一个Montor对象,Monitor对应的是底层的MonitorObject,是基于操作系统的互斥mutex实现的。



ObjectMonitor中有几个关键属性

属性 描述
_owner 指向持有ObjectMonitor对象的线程
_WaitSet 存放处于wait状态的线程队列
_EntryList 存放处于等待锁block状态的线程队列
_recursions 锁的重入次数
_count 用来记录该线程获取锁的次数

  1. 进入monitor,被分配到Entry List中,等待持有锁的线程释放锁,
  2. 当线程获取到锁后,是锁的持有者,owner指向当前线程
  3. 当线程进行wait时进入Wait Set,等待锁的持有者进行唤醒。

synchronized锁的实现原理

  1. 当代码执行到被synchronized修饰的代码块或方法时,首先通过monitor去获取对象实例的锁
  2. 当获取到锁时,会在对象实例的对象头上添加锁标识位
  3. 没有获取到锁的线程,会进行到对对象实例的entry list中进行等待
  4. 持有锁的线程的业务处理完后通过修改对象头上锁标识位来进行释放锁
  5. 当线程进行wait操作时,当前也会释放锁,然后进行wait set区等待被唤醒
  6. entry list中处理等待的线程再次进行锁的竞争

Mark Word

一个对象的创建要经过这几步:

  1. 加载:如果对象的Class还没加载
  2. 链接:由符号引用转换为地址引用
  3. 初始化:执行Class的方法
  4. 开辟一个地址空间(可以使用TLAB技术进行优化,避免通过CAS产生的资源竞争)
  5. 初始化对象头信息
  6. 执行代码的方法

    7.返回对象地址

    一个对象有:对象头实例数据对齐填充三部分组成



    对象头有:对象标记(Mark Word)类型指针组成,如果对象是数组,对象头中还有数组的长度

    在64位系统中,对象标记占8个字节,类型指针占8个字节,对象头共点16个字节

    对象标记中有hashcode码GC年龄锁标记组成



    每个字节占8位,8个字节的MarkWord共占64位

    无锁的状态下,前25位没有使用,紧接着的32位保存了对象的hashcode,在1位未使用,后面的4位对象的GC年龄,后面的3位是锁标记位。

为什么GC年龄不能超过16

在MarkWord中可以看出GC年龄标记只有4位,二进制表示就是:1111,对应的十进制就是15。

下面通过jol进行查看MarkWord的信息,

<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>

无锁时

import org.openjdk.jol.info.ClassLayout;
public class MarkWordTest {
public static void main(String[] args) {
Hummy hummy=new Hummy();
int hashCode = hummy.hashCode();
System.out.println(hashCode);
System.out.println("二进制:"+Integer.toBinaryString(hashCode));
System.out.println("十六进制: "+Integer.toHexString(hashCode));
System.out.println(ClassLayout.parseInstance(hummy).toPrintable());
}
}
class Hummy{}

打印出的结果如下:



可以看到对象的hashcode是:6f496d9f,可以在左边的Value的找到hashcode值,只不过是反过来的。

最后1字节的00000001包含了gc年龄和锁标记位。

加锁时

import org.openjdk.jol.info.ClassLayout;
public class MarkWordTest { public static void main(String[] args) {
//java -XX:BiasedLockingStartupDelay=0
Hummy hummy=new Hummy();
synchronized (hummy){
System.out.println(ClassLayout.parseInstance(hummy).toPrintable());
}
}
}
class Hummy{}



最后一个00000101的最后3位101表示偏向锁

synchronized的优化

jdk1.6之前只有重量级锁,面在java1.6之后对synchronized的锁进行了优化,有偏向锁、轻量级锁、重量级锁,主要是因为重量级锁需要用到操作系统mutex,操作系统实现线程之间的切换需要从用户态到内核态的,成本非常高。

锁标识 场景
无锁 001 不受保护时
偏向锁 101 只有一个线竞争时
轻量级锁 00 竞争不激烈时
重量级锁 10 竞争非常激烈

锁升级的过程:

  1. 当访问同步代码时,首先判断markword是否是无锁状态(001)或者在偏向锁状态下markword中的线程id与当前线程id是否一样,如果是则把当前线程id通过CAS的方式设置到markword中
  2. 设置成功后则锁标记修改为(101),升级为偏向当前线程的编向锁(101),执行同步内的方法
  3. 如果失败,则由jvm进行偏向锁的撤消
  4. 当持有锁的线程运行到安全点时,检查偏向锁的状态
  5. 当持有锁的线程已退出同步方法时,释放原线程持有的锁,变成无锁状态,到1处执行
  6. 当持有锁的线程还在同步代码中,则升级锁为轻量级锁(00),当前线程持有,另个线程通过CAS的方法进行获取锁,当自旋到一定次数(20)时,则升级为重量级锁(10),进入堵塞状态。

【一知半解】synchronied的更多相关文章

  1. 对JVM还一知半解

    对JVM还一知半解?这篇文章让你彻底搞定JVM 摘要: 对于Java开发者来说,想把自身能力提升到更高层次,某些JVM相关知识应该是优先级很高的.比如说GC策略,JVM调优. 就我在工作中遇到的情况来 ...

  2. oracle的一知半解

    这里只讲第一次开发运用oracle数据库的.net程序遇到问题: 1.程序与oracle数据库在同一台的服务器,貌似设置好连接字符串就可以直接访问( 需要主要的问题: 字符串格式:Data Sourc ...

  3. JAVA 锁之 Synchronied

    ■ Java 锁 1. 锁的内存语义 锁可以让临界区互斥执行,还可以让释放锁的线程向同一个锁的线程发送消息 锁的释放要遵循 Happens-before 原则(锁规则:解锁必然发生在随后的加锁之前) ...

  4. Docker容器从一知半解到入门

    Docker是一个开源的.跨平台的应用容器引擎,可以让技术开发认用打包他们的应用以及一些依赖包到一个可移植的容器平台中,发布到任何流行的Linux操作系统上面,也可以在Windows和mac操作系统上 ...

  5. jQuery使用一知半解

    jQuery是目前使用最广泛的javascript函数库.据统计,全世界排名前100万的网站,有46%使用jQuery,远远超过其他库.微软公司甚至把jQuery作为他们的官方库.对于网页开发者来说, ...

  6. babel 用法及其 .babelrc 的配置详解,想做前端架构,拒绝一知半解...

    Babel 官方介绍:将 ECMAScript 2015 及其版本以后的 javascript 代码转为旧版本浏览器或者是环境中向后兼容版本的  javascript 代码. 简而言之,就是把不兼容的 ...

  7. 关于React中props与state的一知半解

    props props英文翻译是道具的意思,我个人理解为参数,如果我们将react组件看作是一个函数,那么props便是函数接收外部数据所使用的参数.props具有以下特性: 1.不可变(只读性) p ...

  8. 从一知半解到揭晓Java高级语法—泛型

    目录 前言 探讨 泛型解决了什么问题? 扩展 引入泛型 什么是泛型? 泛型类 泛型接口 泛型方法 类型擦除 擦除的问题 边界 通配符 上界通配符 下界通配符 通配符和向上转型 泛型约束 实践总结 泛型 ...

  9. [ SQLAlchemy ] 关于dynamic的“一知半解”

    问题提出: 1.粉丝机制 2.评论的点赞功能 这两个功能分别由User类和Comment类来实现,同样定义了多对多的关系,查询的时候用的方法却大不一样,先看看代码吧. ### # User类的中间表 ...

随机推荐

  1. XCTF练习题---MISC---intoU

    XCTF练习题---MISC---intoU flag:RCTF{bmp_file_in_wav} 解题步骤: 1.观察题目,下载附件 2.解压以后是一个音频文件,听一听,挺嗨的,一边听一边想到音频分 ...

  2. Swift字符串操作-持续更新-2022

    Swift字符串追加 var str = "OC" str.append(" Swfit") print(str) // 输出结果: OC Swift 输出结果 ...

  3. jfinal极速开发

    下载jfinal项目,上面都配置好了不用自己新建从头配置.https://jfinal.com/ idea打开项目 配置数据库 resources目录下demo-config-dev.txt # co ...

  4. 阿里CBU技术面试小结

    一个执着于技术的公众号 前言 今天给大家分享一篇胡文兴同学阿里CBU技术面试的自我总结,希望通过本篇文章也让正在准备求职面试的你有所帮助. 本篇文章已经征得原作者同意转载至本公众号,并且征得他的同意标 ...

  5. 2021 CSP-J复赛 我的备战与游记

    目录 备战 2021.10.18 2021.10.19 2021.10.20 2021.10.21 2021.10.22 比赛当日 早上 线下见面 正文 比赛后 赛后总结与讲解 简单总结 Candy ...

  6. IDEA通用配置

    文件的自动author注释

  7. PTA 7-4 堆栈操作合法性 (20 分)

    假设以S和X分别表示入栈和出栈操作.如果根据一个仅由S和X构成的序列,对一个空堆栈进行操作,相应操作均可行(如没有出现删除时栈空)且最后状态也是栈空,则称该序列是合法的堆栈操作序列.请编写程序,输入S ...

  8. MySQL基准测试工具

    一.基准测试 基准测试(benchmark)是针对系统设计的一种压力测试. 基准测试是简化了的压力测试. 1.1 常见指标 TPS QPS 响应时间 并发量 1.2 收集与分析数据脚本 收集数据的sh ...

  9. RabbitMQ消息可靠性、死信交换机、消息堆积问题

    目录 消息可靠性 生产者消息确认 示例 消费者消息确认 示例 死信交换机 例子 高可用问题 消息堆积问题 惰性队列 参考 消息可靠性 确保消息至少被消费了一次(不丢失) 消息丢失的几种情况: 消息在网 ...

  10. Vue2-Slot插槽使用

    Slot插槽 父组件向子组件传递 父组件将内容分发到子组件 slot插槽的值只读,不能在子组件中修改 slot插槽也可以作为组件之间的通信方式 默认插槽 父组件中:使用Son组件 <templa ...