Java锁

本文介绍了Java中的锁机制,包括每个对象自带的锁、锁的状态(无锁、偏向锁、轻量级锁、重量级锁)以及Synchronized的使用。详细解析了锁在对象头中的存储结构,并讨论了锁升级的概念,强调了锁从无锁到重量级锁的演变过程,以及轻量级锁的自旋等待策略。

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

1、什么是锁

在并发环境下,多个线程会对同一资源进行争抢竞争,可能会导致数据不一致的问题出现。为了解决此问题,编程语言引用的锁机制,来对资源进行锁定。可以理解为解决了资源竞争问题的方式就是锁。

当不同的线程竞争灰色区域内的共享数据时,就有可能出现问题。

2、对象

    Java中每个对象object都拥有一把锁,锁存放在对象头中,锁记录了当前对象被哪个线程占用。

其中对象头记录了一些运行时信息(markWord、classPoint等)。

其中markWord中记录了一些很重要的锁的信息(64位虚拟机与32位虚拟机不同):

锁状态

25bit

4bit

1bit

2bit

23bit

2bit

是否偏向锁

锁标志位

无锁

对象的hashCode

分代年龄

0

01

偏向锁

线程id

Epoch

分代年龄

1

01

轻量级锁

指向栈中所记录的指针

00

重量级锁

指向重量级锁的指针

10

GC标记

11

由这32bit的信息,就可以了解到对象锁的信息。

3、Synchronized

看一个简单的demo。可以看到是由MONITORENTER和MONITOREXIT两个字节码指令将业务代码进行包裹。但是因为monitor是依赖于操作系统的mutex lock实现的,所以每次挂起或者唤醒线程时都要切换操作系统的内核态,操作是比较重量级的,甚至切换的时间消耗就大于执行任务的时间(所以其实并不是线程数越多越好)。

Java6引用了偏向锁与轻量级锁的概念。所以也就出现了锁升级的概念。无锁->偏向锁->轻量级锁->重量级锁。(有锁升级,那么有锁降级吗?)

4、锁的4种状态

4.1无锁

无锁没有对资源进行锁定,所有线程均可以对资源进行访问。有两种情况:1、无竞争2、存在竞争但是不使用锁的方式同步线程(经典CAS)。同时CAS在操作系统中是通过一条指令来实现,所以可以保证原子性。

4.2偏向锁

如果需要给对象加锁,同时希望不通过线程切换也不使用CAS来获取锁,而是让对象“认识”某个线程,只要后续是此线程访问此对象,就直接获取锁,这就是偏向锁。偏向锁可以通过前23bit中的线程id来判断,如果相同则直接获取锁,如果不同则升级为轻量级锁。

4.3轻量级锁

最后两个bit位为00时为轻量级锁,此时线程会在自己的虚拟机栈中中开辟出一块Lock Record的空间,虚拟机栈为线程私有的一块空间。Lock Record中存放的是对象头中的markWork的副本以及owner指针,线程通过CAS去尝试获取锁,获取锁之后将会复制对象头中的markWork信息到Lock Record中,同时将owner指针指向该对象,同时对象头中的markWork的30bit将生成一个指针指向虚拟机栈中的Lock Record,这样就实现了线程与对象头的绑定,此时改对象被此线程占有。

 

此时若有其他的线程想要获取对象,则需要自旋等待。线程不断自旋尝试看目标对象的锁有没有被释放,如果释放了就获取。此方式区别于被操作系统挂起阻塞,若对象很快被释放,自旋不需要进行系统中断和恢复,所以效率更高。

当然为了解决一直不停的自旋,CPU空转问题,优化出现了适应性自旋,自旋时间由上一次在同一个锁上的自旋时间以及锁状态共同决定。如果自旋等待的线程超过1个,轻量级锁将升级为重量级锁。

4.4重量级锁

重量级锁完全锁定资源,通过monitor来进行管控,例如synchronized。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wmf_helloWorld

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值