java并发编程系列(二):synchronized的实现原理简介

  synchronized可能Java多线程之间实现同步最常用的方式之一了,但是Java底层是如何实现的?仅在此记录自己的理解

一:java编程中使用synchronized实现线程同步

  1. 对于普通方法,synchronized的锁对象是当前对象(this)
  2. 对于类方法(static方法),synchronized的锁对象是当前类的class对象
  3. 在同步代码块中的锁是括号里的对象

二:实现原理

synchronized实现同步的基础: java中的每一个对象都可以作为锁,都有一个对应的监视器(monitor)当一个线程访问到同步代码块要获取锁,退出代码块和发生异常要释放锁,那么是如何实现的?
       同步方法和同步代码块实现方式都是基于进入和退出monitor对象实现的,具体细节不一样.
       同步代码块方式通过在编译后的代码块开始的地方插入monitorenter指令,在代码块结束的地方或者异常的地方插入monitorexit.当执行到monitorenter时尝试获取锁,当执行到monitorexit时尝试释放锁.
       同步方法是通过调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的

1.Java对象头

Java中对象在内存中分为三部分存储:对象头,实例变量,填充数据.关于这部分有篇博客写的很好:https://blog.csdn.net/lkforce/article/details/81128115.
Java对象头由三部分组成:
1,Mark Word

2,指向类的指针

3,数组长度(只有数组对象才有)

对象头的Mark Word中主要存储锁相关信息,对象头结构:
在这里插入图片描述
Mark Word在默认情况下存储着对象的HashCode、分代年龄、锁标记位等以下是32位JVM的Mark Word默认存储结构:
在这里插入图片描述
在运行期间,Mark word里存储的数据会随着锁标志位的变化而变化,可能有以下四种:
在这里插入图片描述
每一个对象和一个monitor对象关联(监视器),Mark word中的指针就是指向monitor对象
monitor对象中需要关注以下几个数据
2个队列:_WaitSet 和 _EntryList
持有锁的线程id:_owner
当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor后, monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)

2.锁升级

偏向锁:(无竞争)
hotspot的作者研究发现,大多数情况下锁不存在多线程竞争,而且总是由同一线程多次获得.所以为例提高性能,引入了偏向锁.当同一个线程多次获取同一个锁,第二次以后就不需要再通过同步来获取锁.但是如果竞争激烈,锁会升级为轻量级锁

轻量级锁(适用于多线程依次执行同步)
当有一定竞争,偏向锁失败,锁结构变为轻量级锁.将对象头的mark down复制到线程栈中,将mark down执行锁(以上2个操作都是使用CAS),如果成功获取锁成功,反之失败(膨胀为重量级锁)

重量级锁
竞争太激烈的时候,会通过使用自旋方式不断尝试获取锁(CAS)

3:synchronized获取锁的原理

代码块:
当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞(通过CAS等待获取锁),直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令

方法:
JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放

三:参考

https://www.cnblogs.com/paddix/p/5367116.html
https://blog.csdn.net/javazejian/article/details/72828483

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章