面试官:Synchronized与 ReentrantLock区别是什么?
yuyutoo 2024-12-12 15:54 1 浏览 0 评论
基本概念
Synchronized:Synchronized是 Java 中的一个关键字,用于实现线程同步。它可以修饰方法或者代码块,当一个线程访问被synchronized修饰的方法或者代码块时,其他线程必须等待该线程释放锁之后才能访问。例如,在一个多线程环境下的类中:
class SynchronizedExample {
private int count = 0;
// 修饰方法的加锁形式
public synchronized void increment() {
count++;
}
}
在这个例子中,increment方法被synchronized修饰,这是一种对方法整体加锁的形式。当一个线程执行这个方法时,其他线程不能同时执行这个方法,必须等待正在执行的线程完成并释放锁后才能执行。另外,还可以对代码块加锁,如下所示:
class SynchronizedBlockExample {
private Object lock = new Object();
private int count = 0;
public void increment() {
// 对代码块加锁,指定锁对象为lock
synchronized (lock) {
count++;
}
}
}
这种方式可以更灵活地控制加锁的范围,只对需要同步的代码块进行加锁。释放锁是自动完成的,当synchronized修饰的方法或者代码块执行完毕后,锁会自动释放。
- ReentrantLock:ReentrantLock是java.util.concurrent.locks包下的一个类,它也用于实现线程同步。它提供了比synchronized更灵活的锁机制。例如:
import java.util.concurrent.locks.ReentrantLock;
class ReentrantLockExample {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
这里通过ReentrantLock的lock方法获取锁,在finally块中使用unlock方法释放锁,确保锁最终会被释放,防止死锁。这是ReentrantLock典型的加锁形式,在需要同步的代码块开始处调用lock方法获取锁,在代码块结束后的finally块中释放锁,保证无论是否发生异常,锁都能被正确释放。
功能特性方面的区别
可中断性
- Synchronized:不可以中断。如果一个线程正在等待获取被synchronized修饰的资源锁,那么这个等待过程是不可中断的。例如,在一个长时间等待获取锁的场景中,线程无法被外部信号中断等待状态,只能一直等待直到获取到锁。
- ReentrantLock:可以中断。ReentrantLock提供了lockInterruptibly方法,允许一个线程在等待获取锁的过程中被中断。例如,在一个多线程任务中,如果某个线程等待锁的时间过长,可以通过中断机制来终止这个线程的等待状态,这样可以更好地处理一些可能出现的异常或者超时情况。
公平性
- Synchronized:是非公平锁。这意味着当多个线程竞争锁时,没有任何机制保证等待时间最长的线程先获取锁。例如,假设有三个线程 A、B、C 同时竞争一个被synchronized修饰的资源,线程 A 可能在刚刚释放锁后,新请求锁的线程 C 就获取到了锁,而不是等待时间更长的线程 B。
- ReentrantLock:可以通过构造函数设置为公平锁或者非公平锁。当设置为公平锁时,多个线程竞争锁时,等待时间最长的线程会优先获取锁。这样在一些对公平性要求较高的场景下,如资源分配系统,使用公平锁可以保证每个线程都能按照请求锁的顺序来获取锁,避免线程饥饿现象。
锁绑定多个条件(Condition)的能力
- Synchronized:没有直接提供类似于ReentrantLock中Condition的功能。在synchronized中,如果需要实现类似的功能,可能需要通过Object类的wait、notify和notifyAll方法来间接实现,但是这种方式相对比较复杂,而且不够灵活。
- ReentrantLock:可以通过newCondition方法创建多个Condition对象,每个Condition对象可以和锁绑定,用于实现更精细的线程等待和唤醒机制。例如,在一个生产者 - 消费者模型中,可以使用不同的Condition对象来分别控制生产者线程和消费者线程的等待和唤醒,使得代码逻辑更加清晰。
灵活性区别
Synchronized:
- 灵活性相对较差。因为它是 Java 语言的关键字,其使用方式比较固定。一旦使用synchronized修饰方法或者代码块,它的行为就由 Java 语言规范确定,很难进行定制化的操作。例如,无法在运行时动态地改变锁的公平性或者中断等待获取锁的线程。而且,在处理多个等待条件时,通过Object类的wait、notify和notifyAll方法来实现,需要严格遵循一定的规则,否则容易出现死锁或者程序逻辑错误。
- 对于代码结构的影响较大。synchronized关键字直接作用于方法或者代码块,可能会导致代码的结构不够灵活。如果需要对同步的范围或者条件进行修改,可能需要对代码进行较大的改动。例如,如果要将一个被synchronized修饰的方法拆分成多个小方法,并且保证同步逻辑正确,需要仔细考虑每个小方法的同步方式,可能会涉及到重新设计整个同步策略。
ReentrantLock:
- 提供了更高的灵活性。开发者可以根据具体的需求在代码中灵活地控制锁的获取、释放、公平性以及与多个条件的关联等操作。例如,可以在不同的业务逻辑分支中,根据实际情况选择不同的锁获取方式(如lock方法或者lockInterruptibly方法)。而且,可以在运行时动态地改变锁的公平性设置,以适应不同的业务场景。
- 能够更好地适应复杂的业务逻辑变化。由于其灵活的特性,在业务逻辑发生变化时,如需要添加新的等待条件或者改变线程的等待和唤醒策略,ReentrantLock可以通过创建新的Condition对象或者修改已有的Condition对象的操作来实现,而不需要对整个代码结构进行大规模的修改。这种灵活性使得ReentrantLock在处理复杂的多线程同步场景时更加得心应手。
条件队列和加锁、释放锁形式的区别
Synchronized的条件队列相关操作
- 等待条件(wait):在synchronized块中,可以使用Object类的wait方法让当前线程等待。例如,在一个生产者 - 消费者模型中,消费者线程在获取到锁进入synchronized块后,如果发现没有数据可消费,会调用wait方法。这个方法会释放当前持有的锁,并将线程添加到对象的等待队列中,线程进入等待状态,直到被notify或notifyAll方法唤醒。等待队列是和对象关联的,每个对象都有自己的等待队列。
- 唤醒操作(notify和notifyAll):notify方法会随机唤醒等待队列中的一个线程,而notifyAll方法会唤醒等待队列中的所有线程。这些操作必须在持有与wait方法相同对象锁的synchronized块中执行。例如,生产者线程生产了新的数据后,在synchronized块中调用notify或notifyAll来唤醒等待的消费者线程。这种方式相对比较粗糙,因为无法精准地控制唤醒特定条件下的线程。
ReentrantLock的条件队列相关操作(Condition)
- 创建条件队列(newCondition):ReentrantLock通过newCondition方法创建Condition对象,每个Condition对象都代表一个条件队列。例如,在生产者 - 消费者模型中,可以创建两个Condition对象,一个用于生产者线程等待消费者线程消费完数据(producerCondition),另一个用于消费者线程等待生产者线程生产数据(consumerCondition)。
- 等待条件(await)和唤醒操作(signal和signalAll):线程在获取ReentrantLock锁后,可以通过Condition对象的await方法将自己添加到对应的条件队列中等待。例如,消费者线程获取锁后,如果没有数据可消费,通过consumerCondition.await()等待。signal方法会唤醒条件队列中的一个线程,signalAll方法会唤醒所有线程。这些操作更加灵活,因为可以根据不同的Condition对象精准地控制线程的等待和唤醒,实现更复杂的同步逻辑。
加锁和释放锁形式对条件队列的影响
- Synchronized:由于加锁和释放锁是隐式的(方法执行完或者代码块结束自动释放),在使用wait和notify/notifyAll时,必须严格遵循在持有锁的情况下调用这些方法的规则。否则会抛出IllegalMonitorStateException异常。这种隐式的加锁和释放锁形式使得在处理复杂的条件等待和唤醒逻辑时容易出错。
- ReentrantLock:加锁和释放锁是显式的,开发者可以更好地控制锁的范围和时机。在使用Condition对象的await、signal和signalAll操作时,由于锁的显式控制,可以更清晰地保证在正确的时机执行这些操作,避免了synchronized中可能出现的因加锁和释放锁时机不当导致的问题。同时,ReentrantLock可以在不同的业务逻辑阶段灵活地获取和释放锁,方便与Condition对象配合,实现更精细的线程调度和同步。
锁的实现机制
Synchronized:
- 对象头和锁状态:synchronized的实现依赖于对象头中的标记位来存储锁状态信息。在 Java 对象头中,有一部分空间用于存储对象的哈希码、分代年龄等信息,同时也用于表示锁的状态。锁状态主要有偏向锁、轻量级锁和重量级锁。当没有线程竞争锁时,对象可能处于无锁状态或者偏向锁状态。偏向锁是一种优化机制,它会偏向于第一个获取锁的线程,将对象头中的部分标记位设置为该线程的 ID,这样当这个线程再次访问同步块时,不需要进行复杂的锁获取操作,直接就可以进入同步块。
- 锁升级过程:当有其他线程尝试竞争偏向锁时,会触发锁升级。首先会升级为轻量级锁,轻量级锁是通过在栈帧中创建一个锁记录(Lock Record)来实现的。线程会将对象头中的部分信息复制到锁记录中,然后使用 CAS(Compare - and - Swap)操作尝试将对象头中的指针指向自己的锁记录,这个过程是原子操作。如果 CAS 操作成功,线程就获取了轻量级锁;如果失败,表示有其他线程也在竞争锁,此时会进入自旋状态,即线程会不断地尝试获取锁,而不是立即阻塞。如果自旋一定次数后(这个次数可以通过 JVM 参数配置)仍然无法获取锁,轻量级锁会进一步升级为重量级锁。重量级锁会使线程进入阻塞状态,等待操作系统的调度,这个过程涉及到操作系统的内核态和用户态切换,开销较大。
ReentrantLock:
- 基于 AQS 的实现:ReentrantLock是基于AbstractQueuedSynchronizer(AQS)实现的。AQS 是一个用于构建锁和同步器的框架,它维护了一个等待队列(FIFO 队列),用于存储等待获取锁的线程。当一个线程调用ReentrantLock的lock方法获取锁时,会通过 AQS 的acquire方法尝试获取锁。如果获取成功,线程就可以执行同步块中的代码;如果获取失败,线程会被封装成一个Node对象添加到等待队列中,并且线程会进入阻塞状态。AQS 通过一个int类型的变量来表示锁的状态,对于ReentrantLock来说,这个变量用于记录锁的重入次数。当一个线程已经获取了锁,再次调用lock方法时,重入次数会增加;当线程调用unlock方法时,重入次数会减少,直到重入次数为 0 时,锁才会被真正释放,并且会唤醒等待队列中的下一个线程。
- 公平锁和非公平锁的实现差异:在公平锁的实现中,当一个线程调用lock方法获取锁时,会首先检查等待队列中是否有等待时间更长的线程。如果有,它会将自己添加到队列的末尾,等待前面的线程获取并释放锁后,再按照队列顺序获取锁。而在非公平锁的实现中,线程调用lock方法时,会首先尝试通过 CAS 操作直接获取锁,而不考虑等待队列中的其他线程。如果获取成功,就直接获取了锁;如果失败,再按照公平锁的方式将自己添加到等待队列中等待获取锁。这种实现方式使得非公平锁在性能上可能会优于公平锁,因为它减少了线程排队等待的时间,但是可能会导致某些线程长时间无法获取锁,产生线程饥饿现象。
性能方面的区别
在低竞争场景下
- Synchronized:在 Java 早期版本中,synchronized的性能相对较差,但在 Java 6 及以后的版本中,对synchronized进行了大量优化,在低竞争(即很少有线程同时竞争锁)的场景下,synchronized的性能已经和ReentrantLock相近,因为它的实现已经比较高效,并且 JVM 可以对其进行一些优化,如偏向锁、轻量级锁等。
- ReentrantLock:在低竞争场景下,ReentrantLock由于其额外的功能(如可中断、公平性设置等)会带来一些性能开销,它的性能可能稍逊于synchronized,不过这种差异在大多数情况下并不明显。
在高竞争场景下
- Synchronized:在高竞争(多个线程频繁竞争锁)场景下,由于synchronized是基于对象头的锁机制,当锁竞争激烈时,可能会频繁地进行锁升级(从偏向锁到轻量级锁再到重量级锁),这会带来一定的性能损耗。而且一旦升级为重量级锁,线程的阻塞和唤醒等操作会涉及到操作系统层面的系统调用,开销较大。
- ReentrantLock:ReentrantLock在高竞争场景下相对更有优势。它的内部实现采用了一种更灵活的自旋锁(spin lock)机制,在一定程度上可以减少线程阻塞和唤醒的开销。并且,开发者可以根据具体的场景对ReentrantLock进行优化,如合理设置公平性和使用合适的锁获取方法等,以提高在高竞争场景下的性能。
使用场景方面的区别
- 简单同步场景Synchronized:适合简单的线程同步场景,尤其是在代码结构比较简单,对锁的功能要求不高(如不需要中断锁等待、不需要精细的条件控制等)的情况下。例如,在一个单例模式的实现中,使用synchronized来保证线程安全的实例创建是一个简单有效的方式。
- 复杂同步场景ReentrantLock:适用于复杂的多线程同步场景,当需要使用可中断锁、公平锁或者需要基于多个条件进行线程等待和唤醒时,ReentrantLock是更好的选择。例如,在一个多线程资源调度系统中,需要根据不同的资源状态和任务优先级来灵活地控制线程的等待和执行,ReentrantLock结合Condition可以很好地满足这种复杂的需求。
相关推荐
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...
-
- 平面设计基础知识_平面设计基础知识实验收获与总结
-
CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...
-
2025-02-21 16:01 yuyutoo
- 写作排版简单三步就行-工具篇_作文排版模板
-
和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...
- 写一个2048的游戏_2048小游戏功能实现
-
1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...
- 今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……
-
最近的天气暖和得让很多小伙伴们喊“热”!!! 昨天的气温到底升得有多高呢?你家有没有榜上有名?...
- CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
-
之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...
- 你的自我界限够强大吗?_你的自我界限够强大吗英文
-
我的结果:A、该设立新的界限...
- 行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?
-
行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...
-
- 让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
-
去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...
-
2025-02-21 16:00 yuyutoo
- 今年国家综合性消防救援队伍计划招录消防员15000名
-
记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...
- 一起盘点最新 Chrome v133 的5大主流特性 ?
-
1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...
- 竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使
-
style="text-align:center;"data-mce-style="text-align:...
- 学物理能做什么?_学物理能做什么 卢昌海
-
作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...
-
- 你不知道的关于这只眯眼兔的6个小秘密
-
在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...
-
2025-02-21 16:00 yuyutoo
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)