本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云
前两天,我闲着无事,在群里发了一个关于高并发的面试题,今天我来说一说这套面试题的第一小题的第一部分!
1、请描述synchrnoized和reentrantlock的底层实现及重入的底层原理
2、请描述锁的四种状态和升级过程
3、CAS的ABA问题如何解决
4、请谈一下AQS,为什么AQS的底层是CAS + volatile
5、请谈一下你对volatile的理解
6、volatile的可见性和禁止指令重排序是如何实现的
7、CAS是什么
8、请描述一下对象的创建过程
9、对象在内存中的内存布局
10、DCL单例为什么要加volatile
11、解释一下锁的四种状态
12、Object 0 = new Object()在内存中占了多少字节?
13、请描述synchronized和ReentrantLock的异同
14、聊聊你对as-if-serial和happens-before语义的理解
15、你了解ThreadLocal吗?你知道ThreadLocal中 如何解决内存泄漏问题吗?
16、请描述一下锁的分类以及JDK中的应用
17、自旋锁一定比重量级锁效率高吗?
上面是这套面试题的 17 个小题,不会的抓紧学习吧,拉勾和极客时间对应的并发专栏,从我这里购买都有返现!
要回答这个问题,我们先要知道对象在内存中的布局:
已知对象是存放在堆内存中的,对象大致可以分为三个部分,分别是对象头、实例变量和填充字节。
对象头主要是由MarkWord和Klass Point(类型指针)组成,其中Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据。如果对象是数组对象,那么对象头占用3个字宽(Word),如果对象是非数组对象,那么对象头占用2个字宽。(1word = 2 Byte = 16 bit)。
实例变量存储的是对象的属性信息,包括父类的属性信息,按照4字节对齐。
填充字符,因为虚拟机要求对象字节必须是8字节的整数倍,填充字符就是用于凑齐这个整数倍的。
Synchronized 论是修饰方法还是代码块,都是通过持有修饰对象的锁来实现同步,那么 Synchronized 锁对象是存在哪里的呢?答案是存在锁对象的对象头的 MarkWord 中。那么 MarkWord 在对象头中到底长什么样,也就是它到底存储了什么呢?
在 32 位的虚拟机中:
在 64 位的虚拟机中:
上图中的偏向锁和轻量级锁都是在 java6 以后对锁机制进行优化时引进的,synchronized 关键字对应的是重量级锁。
synchronized 在 JVM 中的实现原理,接下来对重量级锁在 Hotspot JVM 中的实现锁讲解。
synchronized 在 JVM 中的实现原理
重量级锁对应的锁标志位是 10,存储了指向重量级监视器锁的指针,在 Hotspot 中,对象的监视器(monitor)锁对象由 ObjectMonitor 对象实现(C++),其跟同步相关的数据结构如下:
ObjectMonitor() {
_count = 0; //用来记录该对象被线程获取锁的次数
_waiters = 0;
_recursions = 0; //锁的重入次数
_owner = NULL; //指向持有ObjectMonitor对象的线程
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
}
光看这些数据结构对监视器锁的工作机制还是一头雾水,那么我们首先看一下线程在获取锁的几个状态的转换:
线程的生命周期存在 5 个状态,start、running、waiting、blocking 和 dead。
对于一个 synchronized 修饰的方法(代码块)来说:
- 当多个线程同时访问该方法,那么这些线程会先被放进 _EntryList 队列,此时线程处于 blocking 状态
- 当一个线程获取到了实例对象的监视器(monitor)锁,那么就可以进入running 状态,执行方法,此时,ObjectMonitor 对象的 _owner 指向当前线程,_count加1表示当前对象锁被一个线程获取
- 当 running 状态的线程调用 wait() 方法,那么当前线程释放 monitor 对象,进入 waiting 状态,ObjectMonitor 对象的 _owner 变为 null,_count 减 1,同时线程进入 _WaitSet 队列,直到有线程调用 notify() 方法唤醒该线程,则该线程重新获取 monitor 对象进入 _Owner 区
- 如果当前线程执行完毕,那么也释放 monitor 对象,进入 waiting 状态,ObjectMonitor 对象的 _owner 变为 null,_count 减 1
那么 synchronized 修饰的代码块/方法如何获取 monitor 对象的呢?
在 JVM 规范里可以看到,不管是方法同步还是代码块同步都是基于进入和退出 monitor 对象来实现,然而二者在具体实现上又存在很大的区别。通过 javap 对 class 字节码文件反编译可以得到反编译后的代码。
(1)Synchronized 修饰代码块:
synchronized 代码块同步在需要同步的代码块开始的位置插入 monitorentry 指令,在同步结束的位置或者异常出现的位置插入 monitorexit 指令;JVM 要保证 monitorentry 和 monitorexit 都是成对出现的,任何对象都有一个 monitor 与之对应,当这个对象的 monitor 被持有以后,它将处于锁定状态。
例如,同步代码块如下:
public class SyncCodeBlock {
public int i;
public void syncTask(){
synchronized (this){
i++;
}
}
}
对同步代码块编译后的 class 字节码文件反编译,结果如下(仅保留方法部分的反编译内容):
public void syncTask();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter //注意此处,进入同步方法
4: aload_0
5: dup
6: getfield #2 // Field i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i:I
14: aload_1
15: monitorexit //注意此处,退出同步方法
16: goto 24
19: astore_2
20: aload_1
21: monitorexit //注意此处,退出同步方法
22: aload_2
23: athrow
24: return
Exception table:
//省略其他字节码.......
可以看出同步方法块在进入代码块时插入了 monitorentry 语句,在退出代码块时插入了 monitorexit 语句,为了保证不论是正常执行完毕(第15行)还是异常跳出代码块(第21行)都能执行 monitorexit 语句,因此会出现两句 monitorexit 语句。
(2)synchronized 修饰方法:
synchronized 方法同步不再是通过插入 monitorentry 和 monitorexit 指令实现,而是由方法调用指令来读取运行时常量池中的 ACC_SYNCHRONIZED 标志隐式实现的,如果方法表结构(method_info Structure)中的 ACC_SYNCHRONIZED 标志被设置,那么线程在执行方法前会先去获取对象的 monitor 对象,如果获取成功则执行方法代码,执行完毕后释放 monitor 对象,如果 monitor 对象已经被其它线程获取,那么当前线程被阻塞。
同步方法代码如下:
public class SyncMethod {
public int i;
public synchronized void syncTask(){
i++;
}
}
对同步方法编译后的 class 字节码反编译,结果如下(仅保留方法部分的反编译内容):
public synchronized void syncTask();
descriptor: ()V
//方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field i:I
5: iconst_1
6: iadd
7: putfield #2 // Field i:I
10: return
LineNumberTable:
line 12: 0
line 13: 10
}
可以看出方法开始和结束的地方都没有出现 monitorentry 和 monitorexit 指令,但是出现的 ACC_SYNCHRONIZED 标志位。
以上就是 synchronized 的底层实现原理,在实际面试时,捡主要的描述即可!
最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!
本文原文出处:业余草: » 阿里面试题:请描述一下synchrnoized的底层实现及重入的实现原理