网站建设合同通用范本,wordpress职场博客,程序员入门先学什么,互联网定制产品网站文章目录 前言对象结构Monitor 对象Synchronized特征原子性可见性有序性可重入锁 锁升级的过程 前言
源码级别剖析Synchronized
对象结构
Synchronized是Java中的隐式锁#xff0c;它的获取锁和释放锁都是隐式的#xff0c;完全交由JVM帮助我们操作#xff0c;在了解Sync… 文章目录 前言对象结构Monitor 对象Synchronized特征原子性可见性有序性可重入锁 锁升级的过程 前言
源码级别剖析Synchronized
对象结构
Synchronized是Java中的隐式锁它的获取锁和释放锁都是隐式的完全交由JVM帮助我们操作在了解Synchronized关键字之前首先要学习的知识点就是Java的对象结构因为Synchronized锁就是存放在Java对象中的Java对象结构如下图所示
可以清晰的看到Java对象由三部分组成分别是对象头、实例数据、填充数据我们的锁就存放在对象头中接下来我们将对对象结构做一个简单的解析
mark-down对象标记字段占8个字节用于存储有关锁的标记位等信息从图中可以看出有哈希值、轻量级锁的标记位、偏向锁标记位等。Klass PointerClass对象的类型指针它就是指向当前对象属于哪个Class类的指针jdk1.8默认开启压缩指针后占用4个字节关闭压缩指针后占用8个字节。对象实际数据这部分内容包括对象的所有成员变量大小由各个成员变量决定比如byte占用1个字节、int占用4个字节等。对其填充这部分内容仅仅只是做到空间补全就是一个占位符的作用因为HotSpot虚拟机的内存管理系统要求对象的起始地址必须是8字节的整数倍因此如果出现对象实例没有对齐的话就需要通过对其填充来补充。
在mark-down锁类型标记中可以看到总共有五种类型分别是无锁、偏向锁、轻量级锁、重量级锁、GC标记所以如果只是使用2比特标记是无法完全被表示出来的所以引入了一位偏向锁标记也就是说001为无锁、101为偏向锁。
Monitor 对象
上面介绍了对象结构可以看到在Mark-down中会存储不同的锁信息当锁的状态为重量级锁10时Mark-down中会存放一个指向Monitor对象的指针这个Monitor对象也称为监视器锁。
synchronized的运行机制就是JVM检测到共享对象存在不同的竞争情况的时候会自动切换到适合的锁实现这种切换就是锁的升级、降级。很多地方都说锁只能升级不能降级其实这种说法是错误的在《Java并发编程的艺术》书中说到对于偏向锁来说它可以进行降级到无锁状态也叫做偏向锁的撤销。
那么现在就存在着三种不同的Monitor实现分别是偏向锁、轻量级锁和重量级锁。如果一个Monitor被一个线程持有的时候就说明这个线程拿到了锁。
Java中的Monitor是基于C的ObjectMonitor实现的它的主要成员包括
_owner指向持有ObjectMonitor对象的线程_WaitSet存放处于wait状态的线程队列即调用wait()方法的线程_EntryList存放处于等待锁Block状态的线程队列_count约为_WaitSet_EntryList的节点数之和_cxq多个线程争抢锁会先存入这个单向链表_recursions记录重入次数_object存储的Monitor对象
获取Monitor对象的线程进入_owner区的时候_count1如果线程调用了wait()方法那么会释放Monitor对象释放锁_owner恢复为空同时_count-1。此时该线程进入_WaitSet队列中等待被唤醒。
从上述的描述可以看出synchronized关键字获取锁的关键在于每个对象的对象头中这也就能解释了为什么synchronized()括号里存放任何对象都能获得锁的特征。
Synchronized特征
原子性
原子性就是说一个操作要么完成要么不完成不存在完成一半的情况也就是说这个操作是不可中断的。
synchronized可以保证同一时间内只有一个线程拿到锁进入到代码块去执行代码这样说如果不能理解那么就想象下面的一个场景有一个厕所只有一个坑位并且厕所还上锁了就是为了防止多人一起上厕所的不文明现象每个人上厕所都必须要去厕所管理员处缴费缴费后才能拿到锁再去上厕所上完厕所再把要是还给厕所管理员synchronized就是厕所管理员保证一次只能有一个人拿到锁并且每个人用完厕所之后都必须要归还钥匙。
接下来看到下面一个同步加方法
public static void add() {synchronized (Demo.class) {counter;}
}将其进行反编译后查看代码
javap -v -p Demo
public static void add();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC , ACC_SYNCHRONIZEDCode:stack2, locals2, args_size00: ldc #12 // class2: dup3: astore_04: monitorenter5: getstatic #10 // Field counter:I8: iconst_19: iadd10: putstatic #10 // Field counter:I13: aload_014: monitorexit15: goto 2318: astore_119: aload_020: monitorexit21: aload_122: athrow23: returnException table:可以看到有两个指令明显和monitor有关
monitorenter在判断拥有同步标识 ACC_SYNCHRONIZED 抢先进入此方法的线程会优先拥有 Monitor 的 owner 此时计数器 1 monitorexit当执行完退出后计数器 -1归 0 后被其他进入的线程获得
可见性
可见性指的是当多个线程访问同一个变量时一个线程修改了这个变量的值其他的线程能够立马感知能看到修改后的值。
而Synchronized拥有可见性因为它加锁和释放锁都有如下语义
线程加锁前必须清空工作内存中共享变量的值从而从主内存中读取最新的共享变量的值。线程释放锁时必须把共享变量的值刷新到主内存中。synchronized的可见性依赖于操作系统内核互斥锁实现相当于JVM中的lockunlock退出代码块时需要刷新共享变量到主内存中这一点和volatile关键字不一样volatile关键字的可见性是依赖于内存屏障也叫内存栅栏来实现的。
有序性
as-if-serial就是保证不管编译器和处理器为了性能优化怎样进行指令重排序都需要保证单线程下的运行结果的正确性。也就是常说的如果在本线程内观察所有的操作都是有序的如果在一个线程观察另一个线程所有的操作都是无序的。 注意这里的有序性和volatile是不一样的它并不是volatile的防止指令重排序。
可重入锁
可重入锁的概念很简单就是一个线程可以多次获取自己持有的对象锁这种锁就是可重入锁同样的释放锁也就需要释放相同数量的锁。synchronized锁对象中就有一个计数器用于存放获取锁的次数也就是重入次数。
锁升级的过程
synchronized 锁有四种交替升级的状态无锁、偏向锁、轻量级锁和重量级这几个状态随着竞争情况逐渐升级。