synchronized底层实现
在Java里面,最基本的互斥同步手段是synchronized
(1)原理
- synchronized关键字经过反编译之后,会在同步块的前后分别形成 monitorenter和monitorexit这两个字节码指令。这两个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象。其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置。
- 当代码执行到monitorenter 指令时,将会尝试获取该对象对应的Monitor的所有权,即尝试获得该对象的锁。当该对象的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有该对象monitor的持有权,那它可以重入这个 monitor ,计数器的值也会加 1。
- 与之对应的执行monitorexit指令时,锁的计数器会减1。倘若其他线程已经拥有monitor 的所有权,那么当前线程获取锁失败将被阻塞并进入到_WaitSet 中,直到等待的锁被释放为止。也就是说,当所有相应的monitorexit指令都被执行,计数器的值减为0,执行线程将释放 monitor(锁),其他线程才有机会持有 monitor 。(当锁计数器为0时,该锁就会被释放)
被synchronized修饰的同步块对同一条线程来说是可重入的。这意味着同一线程反复进入同步块也不会出现自己把自己锁死的情况。
被synchronized修饰的同步块在持有锁的线程执行完毕并释放锁之前,会无条件地阻塞后面其他线程的进入。这意味着无法像处理某些数据库中的锁那样,强制已获取锁的线程释放锁;也无法强制正在等待锁的线程中断等待或超时退出。
(2)对象
在HotSpot中,对象在堆内存中的存储布局划分为:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
- 对象头:(实现synchronized的锁对象的基础)
- 存储对象自身的运行时数据(如hashCode,GC分代年龄,锁状态标志,线程持有的锁等)-----MarkWord
- 类型指针:对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例(如果对象是数据,还必须在对象头中存储数组大小)
- Java虚拟机可以通过普通Java对象的元数据信息确定Java对象大小,但是如果数组的长度不确定的话,就无法通过元数据中的信息推断出述责的大小
- 实例数据:对象真正存储的有效信息
- 对齐填充:不是必然存在的,仅仅起着占位符的作用。HotSpot虚拟机中要求所有对象的大小必须是8字节的整数倍。对象头部分已经是8字节的整数倍了,如果对象实例数据部分没有对齐的话,就需要通过对齐方式填充来补全。