java互斥锁的实现原理_java-深入分析synchronized原理

互斥锁

互斥锁futex,全拼fast userspace mutexes,直翻为快速用户空间互斥器,它是我们上层应用实现锁的最常用方法。futex是一块所有进程都可以访问的内存,是通过cpu的原子操作修改内存中的值来尝试获取琐,如果没有竞争,则直接在用户空间完成操作,无需切换内核空间,以此保证了futex的性能。

synchronized

其实java多线程操作大体就那么几种,基于cas的aqs,synchronized和volatile,这篇文章主要介绍下synchronized。

synchronized是java的关键字,在老版本的jdk中性能表现并不太好,所以有了很多基于cas的Lock,但最近几版jdk都对synchronized做了很多优化,以后synchronized也会作为jdk主推的锁。

synchronized最主要的优化就是引入了升级功能,升级主要分偏向锁、轻量级锁、重量级锁。

synchronized因为有升级和降级,既不会直接粗暴的使用互斥锁,也不会有cas锁在超高并发下多次尝试引起的性能问题,所以相比juc的cas锁,synchronized在大多数情况下性能更好。

synchronized的使用方法主要有3种

//加锁到静态方法上

public static synchronized void test();

//加锁到实例方法上

public synchronized void test();

//加锁到对象上

synchronized(this){

}

// 加锁在静态方法上同加锁到类对象上

synchronized(Test.class){

}

// 加锁到实例方法上同加锁到当前对象上

synchronized(this){

}

总体上说,不管是锁类对象,还是锁其它对象都是锁到一个对象上了。

fd9007271ccc4a544be2aed33d9de8ef.png

这张图很重要,在下面会多次用到,就先放这里了。

偏向锁

java为了支持锁对象,在对象头上做了上图的设计,markword是对象头中的一部分,64位虚拟机占64bit,markword在不同等级锁状态下存储的内容是不同的,上图是锁处于不同状态时markword存储的内容。

其实经过大量测试在我们使用synchronized时,大多数情况并没有发生竞争,很多访问都发生在一个线程里,所以设计了偏向锁,偏向锁的意思就是锁偏向某个线程。

当我们尝试给一个对象加锁时,会有几种情况。

未偏向:如果lock为01时,biased_lock为0,表示没有线程持有这个对象的偏向锁,线程会通过cas的方式修改对象头获取偏向锁。

可重偏向:如果lock为01时,biased_lock为1,表示对象被偏向锁定,这时还会拿epoch与klass(可以理解为某个Class在虚拟机中对应的对象)的mark_prototype的epoch作比较,如果不一致表示可重偏向,线程会通过cas的方式修改对象头获取偏向锁。

已偏向:lock为01,biased_lock为1,epoch与mark_prototype的epoch相等表示锁已偏向,会比较thread字段的threadId,如果一致表示持有锁的是当前线程,可以重入。

如果没有获取到偏向锁,就需要进行一个撤销偏向锁并升级偏向锁的过程,这个过程比较消耗性能,所以当某类对象,比如我们的User.class这个对象的实例有很多次撤销(默认值为40),虚拟机就会更新klass的epoch,表示可重偏向,这就是为啥锁的是对象,判断epoch是去klass判断。

轻量锁

当未获取到偏向锁时,需要通知持有偏向锁的线程撤销偏向锁,竞争线程则进行一个轻量级锁加锁的过程。

偏向锁的撤销:持有锁的线程进入safepoint时,判断持有锁的线程是否在加锁状态,如果是则直接修改markword为轻量级锁,否则释放偏向锁,表示未锁定、

轻量级锁加锁:虚拟机会在当前线程的栈帧中创建一个lock record,并拷贝markword到lockrecord,再通过cas把对象的markword改为偏向锁,ptr_to_lock_record指向lockrecord,如果cas成功则表示成功获取轻量级锁,否则进行自旋尝试,就是常说的自旋锁。

轻量级锁释放:轻量级锁释放时,只要把lockrecord中的markword替换回对象头的markword就释放成功了。

重量级锁

当自旋尝试次数超过阈值(jvm控制的动态值),锁就会进一步升级,升级为重量级锁。

升级为重量锁后,线程就会出现等待、阻塞、唤醒等各种操作,就会涉及到用户态和内核态的切换,所以叫重量级锁,重量级锁的实现就是文中最开始提到的futex互斥锁实现的。

重量级锁既然需要线程的管理机制,自然引入了管程(monitor),java的管程模式类似mesa。

71b06bfdc688e3e3a1fd6879ef94688d.png

contentionList: 所有想要竞争的线程都要进入的队列,又叫cxq。

entrylist: 准备竞争的线程都在这个队列,这个队列只有空的时候才去cxq中拉去,cxq每次只会有一个进去entrylist。

OnDeck:entrylist中的一个线程,一般为最前面的线程,只有OnDeck线程才会去竞争锁、

waitset:当我们调用wait()方法时,线程就会进入waitset,被notity后直接进入entrylist,所以被唤醒的线程比刚参与竞争的线程优先级更高。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值