锁升级
锁的状态分为4种,无锁、偏向锁、轻量级锁、重量级锁
其实涉及到锁的升级,随着线程竞争的加大,从偏向锁->轻量级锁->重量级锁,且升级过程不可逆
问:为什么会有锁升级?直接就上重量级锁不好吗?
答:因为重量级锁性能不好。
问:为什么重量级锁性能不好?
答:因为加锁和解锁过程,涉及到CPU用户态切换到内核态再切换到用户态,这个切换很消耗系统资源。
问:什么是用户态和内核态?
答:内核态:CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序;用户态:只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
问:为什么要区分用户态和内核态?
答:由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级:用户态和内核态,毕竟内核态可以访问内存所有的数据。
问:为什么加锁和解锁过程需要切换?
答:所有的用户程序都是运行在用户态的,但是有时候程序确实需要做一些内核态的操作。加重量级锁时,意味着除了获取到锁的线程,其他线程都是阻塞状态,但是用户态并不能操作线程阻塞,所以得切换到内核态,当解锁完后,又需要切换到用户态继续执行程序。
问:用户态和内核态切换流程?
答:1. 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务.
2. 用户态程序执行陷阱指令
3. CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问
4. 这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数, 并执行程序请求的服务
5. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果
锁的优化过程和逻辑:
重量级锁:属于独占锁,第一个线程获取锁,其他的线程阻塞,等待第一个线程释放锁,这是java内置锁,JDK1.6对应底层操作系统的互斥量,会导致系统在用户态和内核态之间切换,同步成本很高。
自旋锁:如果锁的粒度小,锁持有的时间比较短,那么对于竞争锁的线程而言,因为线程阻塞造成的线程状态切换的时间和锁持有的时间相当甚至更长时,那么减少线程切换就能得到较大的性能提升。(例如我持有锁的时间只有一秒,但是为此线程状态切换时间也要一秒甚至两秒,有点得不偿失),所以基于这种情况引入自旋锁,自旋锁竞争锁失败不会阻塞线程,线程会循环去尝试获取锁直到成功,所以不会涉及到用户态和内核态的切换。
自适应自旋锁:自旋锁会可能存在竞争锁一直失败,一直不断尝试获取,长时间的占用cpu资源的问题,自适应自旋锁通过设置自旋次数来解决“锁竞争时间不确定的问题”,根据上一次的自旋时间类调整下一次自旋的时间。
轻量级锁:自旋锁是为了减少线程切换带来的性能消耗,轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗。使用轻量级锁时,不需要申请互斥量,只需要执行CAS,执行成功则轻量级锁获取成功,执行失败说明有其他线程获得了轻量级锁,目前发生了锁竞争,所以升级为重量级锁。当然如果存在锁竞争但不激烈,可以利用锁自旋,自旋失败后再升级成重量级锁。
偏向锁:在没有实际竞争的情况下,还可以针对部分场景继续优化。如果不仅仅没有实际竞争,且自始至终,使用锁的线程只有一个,那么维护轻量级锁都是浪费。轻量级锁每次申请和释放锁都至少需要一次CAS,但是偏向锁只有初始化时需要一次CAS
参考:
https://www.cnblogs.com/shangxiaofei/p/5567776.html
https://www.jianshu.com/p/36eedeb3f912
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通