MST

星途 面试题库

面试题:Java多线程环境下如何通过锁机制优化资源竞争

假设在一个高并发的Java应用场景中,多个线程频繁访问和修改共享资源,导致资源竞争严重影响性能。请详细描述如何通过合理使用不同类型的锁(如synchronized关键字、ReentrantLock等)来优化这种情况,包括不同锁的适用场景、性能特点以及如何避免死锁等问题。
31.0万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

synchronized关键字

  • 适用场景
    • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。例如在单例模式双重检查锁机制中,对创建实例的方法使用synchronized修饰,确保多线程下实例只被创建一次。
    • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。适用于对整个类的静态资源进行同步访问,如操作类的静态计数器。
    • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。可以更细粒度地控制锁的范围,比如只对部分共享资源操作时,可将这部分代码放在synchronized代码块中,并指定锁对象。
  • 性能特点
    • 在早期JDK版本中,性能较差,因为是重量级锁,涉及操作系统内核态与用户态的切换。
    • 从JDK 6开始,对synchronized进行了优化,引入偏向锁、轻量级锁,性能有了很大提升。在竞争不激烈时,偏向锁能减少锁获取的开销;竞争稍激烈时,轻量级锁通过自旋避免线程进入内核态,提高性能。
  • 避免死锁
    • 按照固定顺序获取锁。比如多个线程都需要获取锁A和锁B,那么都先获取锁A再获取锁B,避免一个线程先获取A再获取B,另一个线程先获取B再获取A这种交叉获取锁的情况。
    • 尽量减少同步代码块的嵌套,嵌套层次越多,死锁风险越高。

ReentrantLock

  • 适用场景
    • 当需要更灵活的锁控制时,如可中断的锁获取、超时获取锁等。例如在一个线程池中任务执行时间较长,在获取锁时可以设置超时时间,如果超时未获取到锁,线程可以做其他处理,避免无限等待。
    • 当需要公平锁时,ReentrantLock默认是非公平锁,但可以通过构造函数设置为公平锁。公平锁按照线程请求锁的顺序分配锁,适用于对线程公平性要求较高的场景,如一些资源分配场景,每个线程都希望能公平地获取资源。
  • 性能特点
    • 在高竞争场景下,ReentrantLock性能比synchronized更好,因为它的非公平锁在竞争时能减少线程切换的开销。
    • 提供了更多的高级功能,如锁中断、锁超时等,这些功能在某些场景下能提高程序的健壮性,但也会带来一定的性能开销。
  • 避免死锁
    • synchronized类似,按照固定顺序获取锁。
    • 使用tryLock方法尝试获取锁,并设置超时时间,避免线程无限等待锁。例如:
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
try {
    if (lock1.tryLock(100, TimeUnit.MILLISECONDS) && lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
        // 执行同步代码
    }
} catch (InterruptedException e) {
    // 处理中断异常
} finally {
    if (lock1.isHeldByCurrentThread()) {
        lock1.unlock();
    }
    if (lock2.isHeldByCurrentThread()) {
        lock2.unlock();
    }
}

总结

  • 在竞争不激烈的情况下,优先使用synchronized关键字,因为从JDK 6起其性能有了较大提升,且使用简单,代码可读性好。
  • 在竞争激烈,且需要灵活控制锁,如可中断锁获取、公平锁等场景下,使用ReentrantLock
  • 无论是使用synchronized还是ReentrantLock,都要注意按照固定顺序获取锁,尽量减少同步代码块嵌套,合理使用锁的特性来避免死锁问题,从而优化高并发场景下共享资源访问的性能。