https://mp.weixin.qq.com/s/2yxexZUr5MWdMZ02GCSwdA
1 synchronized实现原理
synchronized实现同步的基础是:Java中的每一个对象都可以作为锁。具体表现为以下3种形式:
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是Synchonized括号里配置的对象。
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。那么锁到底存在哪里呢?锁里面会存储什么信息呢?
从JVM规范中可以看到,synchronized获取和释放锁就是进入和退出Monitor对象,这个Monitor对象就是上面所说的作为锁的对象。通过Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。
2 Java对象头
由于Java面向对象的思想,在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。
在Java SE1.6之前,synchronized一直都是重量级锁,如果某个线程获得了锁,其它获取锁的线程必须阻塞。在高并发的情况下,会有大量线程阻塞,导致系统响应速度急剧下降;同时不断的获取和释放锁也会导致线程不断切换,给系统造成较大的负担。在Java SE1.6之后,为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。这些锁就存在锁对象的对象头中。
2.1 Java对象头的长度
如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头(1个字宽用来存储数组长度);如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit;在64位虚拟机下,1字宽等于64bit。如表所示:
长度 | 内容 | 说明 |
---|---|---|
32/64bit | Mark Word | 存储对象的hashCode或锁信息等 |
32/64bit | Class Metadata Address | 存储到对象类型数据的指针 |
32/64bit | Array Length | 数组的长度(仅当当前对象为数组时存在) |
2.2 Mark Word的存储结构
Java对象头中以一字宽的长度存储Mark Word,里面包含了对象的hashCode,分代年龄和锁标记位。
锁状态 | 25bit | 4bit | 1bit是否是偏向锁 | 2bit 锁标志位 |
---|---|---|---|---|
无状态锁 | 对象的hashcode | 对象分代年龄 | 0 | 01 |
2.3 Mark Word的变化
运行期间,Mark Word的数据类型会随着锁标志位的改变而改变,一共有四种情况: