探讨java中synchronized关键字的工作机制

探讨java中synchronized关键字的工作机制

背景

synchronized是java语言中用来实现同步机制最常用的关键字,有很多文章都已经将它的用法说的很清晰了,本文只探讨synchronized的底层是怎么实现加锁机制的。

在JDK1.5以前,synchronized关键字在JVM中主要通过mutex(互斥锁)实现,这种锁又称重量级锁,每当有其他线程占用锁后,其他想要获得此锁的线程就得处于阻塞状态,而线程的调度是比较耗费CPU资源的,因此这种锁称为是重量级锁。

synchronized和ReentrantLock的比较

而此时基于Java语言编写的ReentrantLock性能要高于synchronized,ReentrantLock采用独占共享模式的,基于AQS(AbstractQueuedSynchronizer )实现的,在AQS内部,维护一个FIFO的多线程队列,每个要获得此锁的线程都要进入到这个队列,如果已经有现成占用了此锁,那么新来的线程需要通过自旋的形式去尝试获得该锁,经过有限的尝试后,仍未获取成功,线程则会被阻塞住,等待前面的线程释放锁后唤醒。因此java.util.concurrent包中很多类是通过ReentrantLock加锁的。

synchronized机制

JDK1.6以后,优化了synchronized的工作机制,实现了三种类型的加锁机制,分别是:偏向锁、轻量级锁和重量级锁,一般来说一开始默认是加偏向锁,它们会随着竞争的激烈而逐渐升级,并且只会升级不会降级。

synchronized实现这三种机制是通过对象头来实现的。
对象大家都很熟悉,但大家可能没注意过对象头吧,对象头中分为三部分,分别是:Mark World、Class Point、数组长度。
和加锁机制有关的部分是 Mark World,里面的主要内容有:加锁状态、HashCode。
咦,这里面怎么会有HashCode,HashCode有必要存储吗?每次用的时候单独计算一下不行吗?不行,我们知道一个对象的HashCode值是不变的,我们也确实需要它不变,如果在程序的运行过程中HashCode值变来变去,岂不是有很大的bug? 而Object中的HashCode值是根据对象的内存地址计算的,但是经过GC后,对象在内存中的地址可能变化,如果此时再对HashCode值进行计算,则肯定和之前的值不一样,因此我们要将第一次计算出的HashCode值进行存储,之后需要HashCode值则直接从对象头中读出,不过这不是我们要讨论的关键,我们要讨论的是加锁状态。

加锁状态可分为:无锁状态、偏向锁状态、轻量级加锁状态、重量级枷锁状态、GC标记(已经被加锁但还未释放锁的对象不存在被GC)

其中偏向锁的获取和释放对性能影响最小,因为此时只有一个线程(线程1)去尝试获取该锁,如果此时有其他的线程(线程2)也来尝试获取此锁,则线程2发现有其他线程已经占用过此锁,则线程2需要通过自旋的方式竞争此锁,此时该锁升级为轻量级锁。当很多线程去竞争同一把锁时,没竞争到锁的线程会处于阻塞状态,此时CPU调度线程的开销会更大,锁会升级为重量级锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值