synchronized的原理(非公平的)
- synchronized会在代码前后形成了mointorenter和mointorexit这两个指令字节码,这两个指令码都需要一个reference参数来知名需要锁定和解锁的对象。实例方法和类方法的是对象分别是this和class对象
- 在执行mointorenter,尝试获取对象的所,如果对象没被锁定,或者我们已经获取这个对象锁了 ,则把锁的计数器加1,相应地mointorexit会减一,计数器变为0锁就被释放,如果获取锁失败了,则当前线程需要阻塞等待,知道对象锁被另外一个线程释放为止。
- 因为阻塞或唤醒线程都需要操作系统,则需要重用户态切换到内核态
- lock主要比synchronized增加了等待中断,可实现公平锁,以及condition
- cas在并发量大的情况下性能并不好,且会存在ABA问题(可以用版本号解决)
- synchronized的优化
-自旋锁和自适应自旋:自旋需要有个度,如果等待锁时间长,则应该直接挂起,所以采用了自适应自旋。自适应自旋根据在同一个锁是哪个上一次的自旋时间,以及所的拥有者的状态来决定。 - 锁消除,是在JIT运行时期,带一些diamante要求同步,但是检测到不会存在并发竞争,因此去除锁。(还会帮我们消除一些jdk一些方法本身就是synchronized)
- 锁粗化:如果一系列锁都对同一个对象进行返回的加解锁,那么可以把锁范围扩大,只加解锁依次
对象头分为两部分:第一部分用于存储对象自身运行时候的数据如hashcode,gc分带年龄,我们称之为mark word,他是实现轻量级锁和偏向锁的关键,另一部分用户存储指向方法区对象类型的数据的指针,如果是数组对象还会有一个额外的部分存储数组长度,对象头数据是与对象自身定义的数据无关的额外存储成本,所以mark workd 被设计成一个非固定的数据结构,以便在极小的空间存储尽量多的信息,且会复用自己的存储空间,比如32bit的空间 25bit用于存储对象的hashcode码,4bit用户存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0。
image.png-
轻量级锁:在代码进入到同步块的时候,如果此同步对象没有被锁定,即锁标志位为01状态,虚拟机给当前线程的栈帧中国建立一个lock record,用户存储锁对象的mark work拷贝,我们称之为displaced mark work,jvm使用cas尝试把对象的mark word 更新为指向lock record的指针,更新成功,那么线程就拥有这个锁,对象的锁标志位变为00
image.png
更新失败的会检测对象markword 是否指向了当前的线程栈帧,如果是说明已经获取对象锁,直接进入代码,否组就说明锁被抢走了。则直接膨胀为重量级锁,锁标志位10.后续的线程也会进入阻塞状态。
轻量级锁解锁也是通过cas,唤醒成功则同步过程就结束,替换失败,说明有其他线程尝试获取过该所,需要在释放锁的时候,唤醒挂起的线程。
轻量级锁的依据是认为绝大部分操作在同步周期内都不存在竞争,所以直接先通过cas操作更新mark word 不需要重量级锁的开销
- 偏向锁,是在轻量级锁的基础上连cas都不需要操作,其在没有竞争情况下,连lock record 都不需要建立 也不需要cas进行操作。第一个获取偏向锁的线程,在接下里如果没有其他线程获取锁的情况下 都不需要进行同步。偏向锁会把对象头的标志位设置为01,通过cas操作把获取到锁的id记录在对象的mark word中,如果成功 则该线程进入锁的代码不需要同步。
-
当另外一个线程去尝试获取锁,则偏向模式宣告结束,根据锁对象是否被锁定的状态,撤销偏向后恢复到未锁定或者轻量级锁定的状态,后续执行就像轻量级锁一样
image.png
网友评论