ITPub博客

首页 > 数据库 > 数据库开发技术 > 锁的基本性能详解

锁的基本性能详解

原创 数据库开发技术 作者:IT枫斗者 时间:2024-11-14 17:54:49 0 删除 编辑

在 Java 中,锁是多线程同步的基础,但锁的使用也会带来性能问题,尤其是在高并发场景下。
为了提高并发性能,减少锁竞争的开销,Java 提供了一些优化机制。下面是一些常见的锁优化策略和机制:


1. 锁的基本性能问题



锁在并发编程中主要用于保证对共享资源的互斥访问。然而,锁的使用会带来以下几种性能问题:


上下文切换:

线程获取锁时,如果当前锁已被其他线程持有,线程将进入等待状态,直到锁被释放。这时,操作系统可能需要进行线程上下文切换,这会带来性能开销。

锁竞争:

多个线程同时竞争同一个锁时,会导致线程饥饿或阻塞,增加延迟。

锁争用:

当多个线程频繁访问同一段被锁住的代码时,锁的争用会导致性能下降。

为了减少这些性能问题,Java 提供了多种锁优化机制。


2. 锁的优化机制





(1)偏向锁(Biased Locking)

偏向锁 :

是 Java 为了优化单线程访问的性能而引入的一种锁机制。当某个线程第一次获取一个锁时,JVM 会将该锁标记为偏向锁,之后该线程会直接获取该锁而不需要竞争。如果同一时刻没有其他线程竞争该锁,则该锁不会发生任何变化。

适用场景:

适合线程长时间占有锁且无其他线程竞争的情况(例如大部分时间只由一个线程访问的锁)。

实现:

偏向锁的开销相对较小,它减少了锁的竞争,能够快速地让线程获得锁,适用于低并发场景 。

注意:当有其他线程尝试竞争该锁时,偏向锁会被撤销,转换为轻量级锁或重量级锁。


(2)轻量级锁(Lightweight Locking)

轻量级锁 :

是在偏向锁无法满足的情况下引入的一种锁优化。其目的是通过 自旋 来避免阻塞线程,从而减少线程切换的开销。具体实现是,线程在尝试获取锁时,如果锁未被其他线程占用,则使用 CAS(Compare And Swap) 操作来尝试直接获取锁。如果锁已被占用,线程会进入自旋状态,反复尝试获取锁。

适用场景:

适用于锁竞争较少的场景,线程数较少,持有锁的时间短。

实现:

轻量级锁的开销相对较低,当只有少数线程竞争锁时,自旋锁的性能会优于重量级锁。

注意:

自旋锁适用于锁的持有时间短的情况,如果锁持有时间较长,会导致线程反复自旋,消耗 CPU 资源,因此不适合在长时间持有锁的情况下使用。


(3)重量级锁(Heavyweight Locking)

重量级锁 :

是传统的同步锁,即 synchronized 锁。在竞争激烈时,JVM 会将轻量级锁升级为重量级锁,使用操作系统的互斥量(mutex)来管理锁的竞争。

适用场景:

适用于高并发竞争的场景,通常用于线程较多且频繁访问共享资源的情况。

缺点:

重量级锁会导致线程上下文切换,因此在高并发场景下性能较差。


(4)自适应锁(Adaptive Locking)


自适应锁 :

是 Java 中一种动态调整锁策略的机制。JVM 会根据锁的竞争情况选择不同的锁实现。比如,如果在一段时间内只有一个线程访问某个对象,则使用偏向锁;如果有多个线程竞争该对象的锁,则使用轻量级锁或重量级锁。

适用场景:

适用于竞争情况不固定,JVM 能根据实际的运行情况动态调整的场景。


3. 使用锁优化的常见方法





(1)减少锁的粒度

锁的粒度决定了锁定代码块的大小。在设计时,应该尽量 缩小锁的范围,避免将不需要同步的代码也包含在锁的范围内。


例如,如果一个方法中的某些操作不需要同步,而其他操作需要同步,则可以将同步部分单独提取到一个小范围的方法中,这样可以减少锁的持有时间,从而提高并发性。
// 错误的做法:过度同步public void doSomething() {    synchronized(this) {        // 处理耗时的计算        expensiveComputation();    }    // 其他不需要同步的操作    doOtherThings();}
// 优化后的做法:减少同步范围public void doSomething() { // 处理耗时的计算 synchronized(this) { expensiveComputation(); } // 其他不需要同步的操作 doOtherThings();}


(2)使用锁分离(Lock Splitting)

锁分离是指将一个大的锁分成多个较小的锁,以降低锁竞争的可能性。


例如,如果有一个集合类,同时操作集合中的多个部分(如多个桶),可以为每个桶分配一个锁,从而允许多个线程并发地访问不同的桶,而不是对整个集合加锁。


(3)减少锁的持有时间

尽量避免在同步块中执行长时间的计算操作,减少锁的持有时间。可以通过将业务逻辑提取到同步块外部或进行异步处理来减少锁的持有时间。


(4)使用 ReentrantLock 替代 synchronized

ReentrantLock 提供了比 synchronized 更灵活的锁机制。它支持尝试获取锁、定时锁、可中断锁等特性,有助于在复杂的多线程场景下提供更好的性能和控制。
ReentrantLock lock = new ReentrantLock();
lock.lock();try { // 需要同步的代码} finally { lock.unlock();}


(5)使用无锁并发编程(CAS)

通过 java.util.concurrent 包中的无锁类,如 AtomicInteger、AtomicLong、AtomicReference 等,可以避免使用锁来进行线程同步。它们通过 CAS(Compare-And-Swap)机制进行原子性操作,在并发量较大时可以减少锁的开销。


4. 总结



Java 提供了多种优化锁性能的机制,包括偏向锁、轻量级锁和重量级锁等,它们根据不同的并发场景动态选择最适合的锁类型。为了进一步优化性能,开发者可以通过减少锁粒度、减少锁持有时间、使用 ReentrantLock、使用 CAS 等方式,来降低锁的竞争,提升并发性能。







👋 大家好,我是枫哥,一名Java后端开发者!我热衷于探索新技术,并在我的微信公众号上分享关于Java 生态和后端开发的知识。欢迎关注我的公众号,期待与你一起探讨技术的无穷可能!” 目前专注于Java技术分享,覆盖春招、秋招、社招和跳槽相关内容,并提供一对一带徒学习服务。加入 学徒计划,即可享受内推机会和优质资源,签订协议确保就业无忧。此外,我们还推出了‘Java跳槽网’, 为你的求职之路提供全方位支持,助你快速找到理想工作


—END—



原文作者:IT枫斗者;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论
大家好,我是枫哥,🌟阿里云技术专家、📝资深面试官、🌹Java跳蚤网课堂创始人。技术交流vx:itfdz666,拥有多年一线研发经验,曾就职过科大讯飞、美团网、平安等公司。看面试实战视频可以关注b站号:IT枫斗者–Java面试。需要看技术文章,可以关注CSDN:IT枫斗者。

注册时间:2020-05-13

  • 博文量
    183
  • 访问量
    81346