面试题—— synchronized 和 ReentrantLock 的区别

本文比较了Java的synchronized和ReentrantLock,阐述了可重入锁的工作原理,指出ReentrantLock提供了更多的高级功能如等待可中断、公平锁和选择性通知,以及它们在不同场景下的性能差异。
摘要由CSDN通过智能技术生成

(1)两者都是可重入锁

可重入锁指的是在一个线程中可以多次获取同一把锁,比如:一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁,两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

  • synchronized:计数器涉及到ObjectMonitor类中的_count变量(数值为0表示没有线程占用锁,数值为1表示有线程占用锁)和_recursion变量(数值表示同一个线程对锁的重入次数)。一个线程执行带锁的方法A第一次获取锁,此时_count=1,recursion=1,接着该线程调用另一个需要相同锁的方法B再次获取锁,此时_count=1,recursion=2,方法B调用完成释放一次锁,此时_count=1,recursion=1,最后方法A调用完成,_count=0,recursion=0。(期间若有其他线程竞争锁,_count保持不变,该线程进入EntryList队列,转为blocked状态)
  • ReentrantLock:计数器涉及到AQS类中的state变量,为0表示没有线程占用锁,大于0表示有线程占用锁,该线程对锁的重入次数。

(2)synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API

synchronized 是依赖于 JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们;

ReentrantLock 是 JDK 层面实现的(也就是个 API ,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

  • synchronized 是 Java 语言内置的关键字,是在 JVM 层面实现的。JVM 负责对 synchronized 进行底层的锁管理,包括锁的获取、释放、锁的升级和降级等操作。
  • ReentrantLock 是 JDK 提供的锁实现,是一个基于接口和类的 API。它在 Java 层面使用了一些底层的同步器类(例如AbstractQueuedSynchronizer)来实现锁的管理。

(3)ReentrantLock 比 synchronized 增加了一些高级功能

主要来说主要有三点:

  • 等待可中断,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情;
  • 可实现公平锁,ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReentrantLock默认情况是非公平的,可以通过ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的;
  • 可实现选择性通知(锁可以绑定多个条件),一个ReentrantLock对象中可以创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。而synchronized相当于一个ReentrantLock对象中只有一个Condition实例,所有线程注册在该实例身上,在使用notify()/notifyAll()方法进行通知时,由JVM随机选一个线程唤醒或者将全部线程唤醒。用ReentrantLock类结合Condition实例,调用signalAll()方法,在该Condition实例上注册的所有等待线程全部唤醒,可以实现“选择性通知”。(调用某个Condition实例的signal()方法,在该Condition的列表中唤醒firstWaiter指向的线程(最先放进去的线程))

(4)性能

JDK1.6之前,synchronized性能远逊于ReentrantLock,JDK1.6之后(包括JDK1.6)synchronized性能和ReentrantLock持平。

  • synchronized 在 JDK 1.6 以后经过了许多优化,性能已经得到了很大的提升。它使用了偏向锁、轻量级锁、重量级锁等技术,以提高并发性能。
  • ReentrantLock相对于synchronized,在低竞争、低并发的情况下,性能可能略逊一筹。

(5)用途

在JDK1.6版本及之后,性能不再是选择synchronized或者ReentrantLock的决定因素。如果有复杂功能需求(比如公平锁,选择性通知等),则选择ReentrantLock。而在没有复杂功能需求的情况下,优先考虑synchronized。

  • synchronized是在Java语言内置的关键字,足够清晰,也足够简单。 每个Java程序员都熟悉synchronized,但JUC中的Lock接口则并非如此。 因此在只需要基础的同步功能时, 更推荐synchronized。
  • Lock应该确保在finally块中释放锁, 否则一旦受同步保护的代码块中抛出异常, 则有可能永远不会释放持有的锁。这一点必须由程序员自己来保证, 而使用synchronized的话则可以由Java虚拟机来确保即使出现异常, 锁也能被自动释放
  • 尽管在JDK 5时代ReentrantLock曾经在性能上领先过synchronized, 但这已经是十多年之前的胜利了。 从长远来看, Java虚拟机更容易针对synchronized来进行优化, 因为Java虚拟机可以在线程和对象的元数据中记录synchronized中锁的相关信息, 而使用JUC中的Lock的话, Java虚拟机是很难得知具体哪些锁对象是由特定线程所持有的。

参考

  1. synchronized 和 ReentrantLock 有什么区别? | JavaGuide(Java面试 + 学习指南)
  2. ChatGPT
  3. 《深入理解Java虚拟机》——13.2.2 线程安全的实现方法
ReentrantLockJava中的一个可重入锁,它提供了与synchronized关键字类似的功能,但更加灵活和强大。下面是一些与ReentrantLock锁相关的面试题及其答案: 1. 什么是可重入锁? 可重入锁是指同一个线程可以多次获得同一个锁,而不会造成死锁。ReentrantLock就是一个可重入锁。 2. ReentrantLock相对于synchronized关键字有什么优势? 相对于synchronized关键字,ReentrantLock提供了更多的功能和灵活性。它可以实现公平锁和非公平锁,支持多个条件变量,可以中断等待锁的线程,还可以尝试获取锁等。 3. ReentrantLock如何实现可重入性? ReentrantLock通过记录持有锁的线程和持有次数来实现可重入性。当一个线程再次获取已经持有的锁时,它的持有次数会增加,当释放锁时,持有次数会减少,只有当持有次数为0时,其他线程才能获取该锁。 4. ReentrantLock如何实现公平性和非公平性? ReentrantLock可以通过构造函数来指定是公平锁还是非公平锁。公平锁会按照线程请求的顺序来获取锁,而非公平锁则允许插队,可能会导致某些线程一直获取不到锁。 5. ReentrantLock如何实现条件变量? ReentrantLock提供了Condition接口来支持条件变量。通过调用ReentrantLock的newCondition()方法可以创建一个Condition对象,然后可以使用该对象的await()、signal()和signalAll()方法来实现线程的等待和唤醒。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值