【一知半解】synchronied
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 | 用来记录该线程获取锁的次数 |

- 进入monitor,被分配到
Entry List中,等待持有锁的线程释放锁, - 当线程获取到锁后,是锁的持有者,
owner指向当前线程 - 当线程进行
wait时进入Wait Set,等待锁的持有者进行唤醒。
synchronized锁的实现原理
- 当代码执行到被
synchronized修饰的代码块或方法时,首先通过monitor去获取对象实例的锁 - 当获取到锁时,会在对象实例的
对象头上添加锁标识位 - 没有获取到锁的线程,会进行到对对象实例的
entry list中进行等待 - 持有锁的线程的业务处理完后通过修改
对象头上锁标识位来进行释放锁 - 当线程进行
wait操作时,当前也会释放锁,然后进行wait set区等待被唤醒 - 在
entry list中处理等待的线程再次进行锁的竞争
Mark Word
一个对象的创建要经过这几步:
- 加载:如果对象的Class还没加载
- 链接:由符号引用转换为地址引用
- 初始化:执行Class的方法
- 开辟一个地址空间(可以使用TLAB技术进行优化,避免通过CAS产生的资源竞争)
- 初始化对象头信息
- 执行代码的方法
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 | 竞争非常激烈 |
锁升级的过程:
- 当访问同步代码时,首先判断markword是否是
无锁状态(001)或者在偏向锁状态下markword中的线程id与当前线程id是否一样,如果是则把当前线程id通过CAS的方式设置到markword中 - 设置成功后则锁标记修改为(101),升级为偏向当前线程的
编向锁(101),执行同步内的方法 - 如果失败,则由jvm进行偏向锁的撤消
- 当持有锁的线程运行到
安全点时,检查偏向锁的状态 - 当持有锁的线程
已退出同步方法时,释放原线程持有的锁,变成无锁状态,到1处执行 - 当持有锁的线程
还在同步代码中,则升级锁为轻量级锁(00),当前线程持有,另个线程通过CAS的方法进行获取锁,当自旋到一定次数(20)时,则升级为重量级锁(10),进入堵塞状态。

【一知半解】synchronied的更多相关文章
- 对JVM还一知半解
对JVM还一知半解?这篇文章让你彻底搞定JVM 摘要: 对于Java开发者来说,想把自身能力提升到更高层次,某些JVM相关知识应该是优先级很高的.比如说GC策略,JVM调优. 就我在工作中遇到的情况来 ...
- oracle的一知半解
这里只讲第一次开发运用oracle数据库的.net程序遇到问题: 1.程序与oracle数据库在同一台的服务器,貌似设置好连接字符串就可以直接访问( 需要主要的问题: 字符串格式:Data Sourc ...
- JAVA 锁之 Synchronied
■ Java 锁 1. 锁的内存语义 锁可以让临界区互斥执行,还可以让释放锁的线程向同一个锁的线程发送消息 锁的释放要遵循 Happens-before 原则(锁规则:解锁必然发生在随后的加锁之前) ...
- Docker容器从一知半解到入门
Docker是一个开源的.跨平台的应用容器引擎,可以让技术开发认用打包他们的应用以及一些依赖包到一个可移植的容器平台中,发布到任何流行的Linux操作系统上面,也可以在Windows和mac操作系统上 ...
- jQuery使用一知半解
jQuery是目前使用最广泛的javascript函数库.据统计,全世界排名前100万的网站,有46%使用jQuery,远远超过其他库.微软公司甚至把jQuery作为他们的官方库.对于网页开发者来说, ...
- babel 用法及其 .babelrc 的配置详解,想做前端架构,拒绝一知半解...
Babel 官方介绍:将 ECMAScript 2015 及其版本以后的 javascript 代码转为旧版本浏览器或者是环境中向后兼容版本的 javascript 代码. 简而言之,就是把不兼容的 ...
- 关于React中props与state的一知半解
props props英文翻译是道具的意思,我个人理解为参数,如果我们将react组件看作是一个函数,那么props便是函数接收外部数据所使用的参数.props具有以下特性: 1.不可变(只读性) p ...
- 从一知半解到揭晓Java高级语法—泛型
目录 前言 探讨 泛型解决了什么问题? 扩展 引入泛型 什么是泛型? 泛型类 泛型接口 泛型方法 类型擦除 擦除的问题 边界 通配符 上界通配符 下界通配符 通配符和向上转型 泛型约束 实践总结 泛型 ...
- [ SQLAlchemy ] 关于dynamic的“一知半解”
问题提出: 1.粉丝机制 2.评论的点赞功能 这两个功能分别由User类和Comment类来实现,同样定义了多对多的关系,查询的时候用的方法却大不一样,先看看代码吧. ### # User类的中间表 ...
随机推荐
- come on! 基于LinkedHashMap实现LRU缓存
/** * @Description 基于LinkedHashMap实现一个基于'LRU最近最少使用'算法的缓存,并且最多存MAX个值 * @Author afei * @date:2021/4/25 ...
- EF Core 的 Code First 模式
0 前言 本文正文第一节,会对 Code First 进行基本的介绍,以及对相关名词进行说明,读者一开始可以不用在这里消耗过多时间,可以先操作一遍例子,再回过头理解. 第二节,以一个简单的例子,展示 ...
- python matplotlib在mac os x 中如何显示中文,完美解决
一. 下载相关的中文字体 simhei 文件: 下载地址 二.通过以下代码查找matplotlib的数据存放位置: import matplotlib print(matplotlib.matplot ...
- 3D打印CLI文件格式的读取
CLI 英文全称:COMMON LAYER INTERFACE 中文名称:通用层接口. 推荐从官方网址看一下其完整的内容: (https://www.hmilch.net/downloads/cli_ ...
- kernel heap bypass smep,smap && 劫持modprobe_path
kernel heap bypass smep,smap && 劫持modprobe_path exp1 smep:smep即用户数据不可执行,当 CPU 处于 ring0 模式时,执 ...
- 使用VLL技术实现多家合作伙伴复用同一条链路做两端数据全透传
公司A当前租用一条10G跨市运营商光缆,自身业务只用到一半流量,为节省成本,寻求多家合作伙伴共用链路以达到财务需求 合作伙伴需求接入链路全透传,即光缆两端接入点端口逻辑直连 当前有三种方案可以实现上述 ...
- ubuntu 20.04 source mirror(aliyun)
x64 deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse deb-src http://m ...
- Python技法:用argparse模块解析命令行选项
1. 用argparse模块解析命令行选项 我们在上一篇博客<Linux:可执行程序的Shell传参格式规范>中介绍了Linux系统Shell命令行下可执行程序应该遵守的传参规范(包括了各 ...
- awd平台搭建及遇到的问题
1.安装docker环境 a.使用的是ubuntu系统,通过sudo apt install docker.io进行docker得安装,此方式会自动启动docker服务. b.通过curl -s ht ...
- Redis 中的原子操作(1)-Redis 中命令的原子性
Redis 如何应对并发访问 Redis 中处理并发的方案 原子性 Redis 的编程模型 Unix 中的 I/O 模型 thread-based architecture(基于线程的架构) even ...