java并发-锁

本文详细介绍了Java并发优化中的轻量级锁、自旋锁和偏向锁的工作原理及优化策略。轻量级锁在无竞争时提供高效加锁,自旋锁用于减少线程阻塞,而偏向锁则优化了单线程访问的场景。文章还探讨了锁升级、锁消除等并发控制机制,帮助理解Java多线程的底层实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是synchronizd

假设有两个方法同步块,利用同一个对象加锁。

 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word

让锁记录中的Object reference 指向锁对象,并尝试用cas替换Object的Mark word,将Mark Word的值存入锁记录。

如果cas替换成功,对象头中存储了 锁记录地址和状态  00,表示该线程给对象加锁,这时图示如下

 如果cas失败,有两种情况。

   如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀的过程

   如果是自己执行了synchronized锁重入,那么再填一条Lock Record作为重入的计数。

当退出synchronized代码块时(解锁)如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一。

当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头,

   成功,则解锁成功。

  失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。

 锁膨胀

如果在尝试加轻量级锁的过程中,cas操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁 

 这时Thread-1加轻量级锁失败,进入锁膨胀流程。

    即为Object对象申请Monitor锁,让Object指向重量级锁地址

    然后自己进入Monitor的EntryList  BLOCKED

 当Thread-0退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的BLOCKED线程。

自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞(避免发生上下文切换)。

自旋重试成功的情况:(自旋是多核cpu的情况,因为自旋是要使用cpu的)

自旋重试失败的情况

在Java6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次,反之 ,就少自旋甚至不自旋,总之,比较智能。

自旋会占用cpu的时间,单核cpu自旋就是浪费,多核cpu自旋才能发挥优势。

Java7之后不能控制是否开启自旋功能。

偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行cas操作(比较和替换:每次产生一条锁记录(Lock Record)的时候就会尝试把对象中的Mark Word对象头替换成新的锁记录的地址。当发生锁重入的时候执行cas操作就不能替换了)。

Java6中引入了偏向锁来做进一步的优化:只有第一次使用cas将线程ID设置到对象的Mark Word头中,之后发现这个线程ID是自己的就表示没有竞争,不用重新cas。以后只要不发生竞争,这个对象就归该线程所有。

 

偏向状态 

偏向锁的延迟:

注意:* biased_lock:0表示无锁。biased_lock:1表示偏向锁。 

          * 当一个对象调用hashcode这个方法的时候会禁用掉偏向锁,给这个对象是无锁。因为没有地方来存储这个hash码了。那为啥轻量级锁和重量级锁调用hashcode这个方法不会有这个问题?因为轻量级锁会把hashcode存储到线程栈帧的锁记录里,重量级锁会存储到Moitor对象里。

这段虚拟机参数表示偏向锁不用延迟加载。

注意:

处于偏向锁的对象解锁后,线程ID仍存储于对象头中 

撤销偏向锁 

1.调用hashcode方法

具体如上:偏向锁状态。

2.其他线程使用对象(其他线程使用该对象的时间与该线程的使用时间不冲突,则升级为轻量级锁,冲突则升级为重量级锁(只有重量级锁才能阻塞))

3.调用wait/notify方法

 因为只有重量级锁才有重量级锁才有。

批量重偏向

 假设有30个对象都是偏向t1线程,但是后来所有对象又在不同时间的t2线程上进行加锁。这时就会发生,偏向锁变为轻量级锁,但当第20个对象的时候,不会变为轻量级锁,就会重新偏向为t2线程。如下代码:

批量撤销

当撤销偏向锁阈值超过40次后,jvm会觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。

锁消除

对于一些没有必要加锁的对象,jvm默认会进行锁消除的优化(去掉锁),通过VM参数设置可以关闭这个优化。(-EliminateLocks)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值