老王:开讲啦,开讲啦,小陈快来上课,今天我们就来探讨一下synchronized底层到底是怎么加锁的?
小陈:哈哈,这个我可等了好久了。
从我接触java开始,就知道了java里面有synchronized这个关键字,是用来加锁的,但是它底层是怎么加锁的我一直没搞明白。
看了一些文章,说通过一个monitor监视器还有什么monitorenter、monitorexit这两条执行来进行加锁和释放锁的,但是具体底层一点的细节我就不知道了,一直是我单身二十几年的一个遗憾啊......
老王:哦,听你这么说,看来你还研究过啊,那你来说说怎么通过monitor和monitorenter和monitorexit来加锁和释放锁的?
小陈:好啊,我结合之前看过的一些文章,画个图来说一下我的理解:
(1)首先java里面每个对象JVM底层都会为它创建一个监视器monitor,这个是JVM层次为我们保证的。这个监视器就类似一个锁,哪个线程持有这个monitor的操作权,就相当于获取到了锁
(2)其次synchronized 修饰的代码或者方法,底层会生成两条指令分别为monitorenter、monitorexit。
(3)进入synchronized的代码块之前会执行monitorenter指令,去申请monitor监视器的操作权,如果申请成功了,就相当于获取到了锁。
如果已经有别的线程申请成功monitor了,这个时候它就得等着,等别的线程执行完synchronized里面的代码之后就会执行monitorexit指令释放monitor监视器,这样其它在等待的线程就可以再次申请获取monitor监视器了。
老王:你说的大致正确,但是表面了一点,这个很多文章都讲到了。但是其实synchronized底层加锁其实涉及很多细节的东西。
小陈:嗯嗯,我看很多文章基本都是只是讲到了通过monitorenter、monitorexit指令去获取monitor的操作权,也就是获取锁,但是到底是怎么获取的?monitor又是个啥东西?为什么monitor能当做锁? 这些更底层的细节我就不知道了。
老王:哈哈,我会讲得通俗一点,尽量让你能听得懂......
老王:首先既然你知道每个对象都有一个monitor监视器,那你知道每个对象是怎么和它的monitor监视器关联起来的不?
小陈:额额额,这个不懂......
老王:说起这个对象和monitor的关联关系,我首先给你讲一下java对象的结构
JAVA对象结构
老王:假如说有一个Test类如下:
public class Test {
private int a = 8;
private ArrayList list = new ArrayList();
}
老王:那么Test对象结构如下图所示:
一个java实例对象由三部分组成,分别是:
对象头:对象头由Mark Word 和 一个指向一个类对象的指针组成。
实例数据:存放这个实例的一些属性信息,比如有的属性是基本类型,那就直接存储值;如果是对象类型,存放的就是一个指向对象的内存地址。
对其填充:主要是补齐作用,JVM对象的大小比如是8字节的整数倍,如果 (对象头 + 实例变量 )不是8的整数倍,则通过对齐填充来补齐。
比如 (对象头 + 实例变量) 部分的大小是20个字节,不是8的整数倍,那么对齐填充这里就会补上4个字节,使得(对象头 + 实例变量 + 对其填充)= 24字节,为8的整数倍。
老王:上面就是一个对象的结构,这个小陈你听懂了吗?
小陈:有点懂,又有点不懂,能不能再画个图说一下,对象头,实例变量里面是啥东西?
老王:没问题,比如说Test test = new Test(),我就来再画个图细说一下test对象的结构:
如上图所示:
(1)对象头:由Mark Word 和一个指向类对象的指针组成
(2)实例变量:记录这个对象哪些属性,如果是基本类型,直接就记录值了;如果是对象类型,则记录一下对象在堆内存的地址,方便以后找到它来使用
(3)对其填充:这块东西就是为了对象的大小满足8的整数倍,进行补齐的,别的鸟用没有
老王:小陈,我这么说,你理解了没?
小陈:哇塞,牛逼啊,你这么说整个对象的结构一目了然......
小陈:老王啊,上面那张图,我还有一个问题,那个Mark Word到底是个啥东西?
老王:Mark Word啊,那就是我们后面讲解的重点了,我们通过synchronized进行加锁,就是通过Mark Word关联起来的。
老王:为了讲清楚Mark Word,我写了一篇Mark Word的自述,让它自己说下它是干啥用的。
JVM世界的Mark Word自述
大家好,我是来自JVM世界的Mark Wold,是一个32位的数据结构,存在于对象头里面,大致结构如下:
大家都知道,计算机是由0和1组成的,每bit位不是0就是1,所以一个bit位能表示两种不同的意思,所以啊,我具有32个bit位,能表示的意思非常多。
JVM就根据Mark Word上32bit的值不同,把我设计为一个多功能的复用器,在bit标志位不同的时候表示的意思也不一样,我前面30bit位可能表示的意思不一样,但是最后2个bit表示的都是锁模式,下面我用表格说明一下我的作用:
(1)首先我的最后两位,也就是锁标志位,分别标识处于不同的锁模式;倒数第3位是偏向锁标志
(2)当我的偏向锁标志是0,锁标志位是01,也就是最后3位是001的时候,我表示无锁模式。作为Mark Word的我就是记录的数据就是对象的hashcode 和 GC的年龄
(3) 当我的偏向锁标志是1,锁标志是01,也就是最后三位是101的时候,处于偏向锁模式,我作为Mark Word这个时候记录的数据就是是获取偏向锁的线程ID、Epoch、对象GC年龄:
(4)当我的锁标志位是00的时候,表示处于轻量级锁模式。我会把锁记录放在加锁的线程的虚拟机栈空间中,所以这种情况下,锁记录在哪个线程虚拟机栈中,就表示所在线程就获取到了锁。
然后我作为Mark Word记录的数据就是就指向那个锁记录地址就好了,这个锁记录地址在哪个线程中,就表示哪个线程获取到了轻量级锁。
(5)当我锁标志位是10的时候,表示处于重量级锁模式,这个时候就说明竞争激烈了,处于重量级锁模式了,由于使用重量级加锁不是我的职责范围,是我的哥们monitor的职责,我这里有它的地址,你们去那里找他吧。
这个是我作为Mark Word 记录的数据就是我哥们monitor的地址,你们有加锁的需求直接根据我提供的这个地址找到monitor,找它加锁就好了。
老王:小陈,这就是这一节要讲的JAVA对象结构以及Mark Word,你听懂了吗?
小陈:懂了,懂了,牛逼啊,老王。感觉你把Mark Word的裤子都给扒了啊......
老王:混小子,有你这么说话的......
小陈:嘿嘿,老王,Mark Word里面的结构我是看懂了,但是我看好多人说底层可以通过Mark Word 可以进行锁升级,无锁、偏向锁、轻量级锁、重量级锁、重量级锁中的自旋等,这个它是怎么操作的?monitor又是个什么结构?以及在重量级锁的时候怎么通过monitor进行加锁的?这些问题我都没明白
老王:别急啊,小陈,这些东西我们下面都会来讲到的,现在我们先顺着思路来一点点讲解。
小陈:好啊,老王,那我们接下面学习什么?
老王:下一篇,我们顺着思路来讲讲《synchronized底层是怎么通过monitor进行加锁的? 》
小陈:好的,老王,我们下一章见面。
目录
JAVA并发专题 《筑基篇》
1.什么是CPU多级缓存模型?
2.什么是JAVA内存模型?
3.线程安全之可见性、有序性、原子性是什么?
4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?
JAVA并发专题《练气篇》
5.volatile怎么保证可见性?
6.什么是内存屏障?具有什么作用?
7.volatile怎么通过内存屏障保证可见性和有序性?
8.volatile为啥不能保证原子性?
9.synchronized是个啥东西?应该怎么使用?
10.synchronized底层之monitor、对象头、Mark Word?
11.synchronized底层是怎么通过monitor进行加锁的?
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
13.synchronized怎么保证可见性、有序性、原子性?
JAVA并发专题《结丹篇》
15.unsafe类的CAS是怎么保证原子性的?
16.Atomic原子类体系讲解
17.AtomicInteger、AtomicBoolean的底层原理
18.AtomicReference、AtomicStampReference底层原理
19.Atomic中的LongAdder底层原理之分段锁机制
20.Atmoic系列Strimped64分段锁底层实现源码剖析
JAVA并发专题《金丹篇》
21.AQS是个啥?为啥说它是JAVA并发工具基础框架?
22.基于AQS的互斥锁底层源码深度剖析
23.基于AQS的共享锁底层源码深度剖析
24.ReentrantLock是怎么基于AQS实现独占锁的?
25.ReentrantLock的Condition机制底层源码剖析
26.CountDownLatch 门栓底层源码和实现机制深度剖析
27.CyclicBarrier 栅栏底层源码和实现机制深度剖析
28.Semaphore 信号量底层源码和实现机深度剖析
29.ReentrantReadWriteLock 读写锁怎么表示?
JAVA并发专题《元神篇》并发数据结构篇
31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?
32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?
33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?
34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?
35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?
36.DelayQueue 底层源码剖析,延时队列怎么实现?
37.SynchronousQueue底层原理解析
JAVA并发专题《飞升篇》线程池底层深度剖析
39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?
40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?
ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?
ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?
ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?
ThreadPoolExecutor shutdown、shutdownNow内部核心流程
再回头看看为啥不推荐Executors提供几种线程池?
ThreadPoolExecutor线程池篇总结