百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

面试官:Synchronized与 ReentrantLock区别是什么?

yuyutoo 2024-12-12 15:54 2 浏览 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可以很好地满足这种复杂的需求。

相关推荐

Mysql和Oracle实现序列自增(oracle创建序列的sql)

Mysql和Oracle实现序列自增/*ORACLE设置自增序列oracle本身不支持如mysql的AUTO_INCREMENT自增方式,我们可以用序列加触发器的形式实现,假如有一个表T_WORKM...

关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)

概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...

MySQL CREATE TABLE 简单设计模板交流

推荐用MySQL8.0(2018/4/19发布,开发者说同比5.7快2倍)或同类型以上版本....

mysql学习9:创建数据库(mysql5.5创建数据库)

前言:我也是在学习过程中,不对的地方请谅解showdatabases;#查看数据库表createdatabasename...

MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别

执行"CREATETABLE新表ASSELECT*FROM原表;"后,新表与原表的字段一致,但主键、索引不会复制到新表,会把原表的表记录复制到新表。...

Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出

在街上看到的PandaDunk的超载可能让一些球鞋迷们望而却步,但Dunk的浪潮仍然强劲,看不到尽头。我们看到的很多版本都是为女性和儿童制作的,这种新配色为后者引入了一种令人耳目一新的新选择,而...

美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍

多功能雷达AN/SPY-1的特性和技术能力,该雷达已经在美国海军服役了30多年,其修改-AN/SPY-1A、AN/SPY-1B(V)、AN/SPY-1D、AN/SPY-1D(V),以及雷神...

汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)

全面分析汽车音响使用或安装技术常识一:主机是大多数人最熟习的音响器材,有关主机的各种性能及规格,也是耳熟能详的事,以下是一些在使用或安装时,比较需要注意的事项:LOUDNESS:几年前的主机,此按...

【推荐】ProAc Response系列扬声器逐个看

有考牌(公认好声音)扬声器之称ProAcTablette小音箱,相信不少音响发烧友都曾经,或者现在依然持有,正当大家逐渐掌握Tablette的摆位设定与器材配搭之后,下一步就会考虑升级至表现更全...

#本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱

作者:初吻给了烟sco混迹张大妈时日不短了,手没少剁。家里有了汪星人,吸尘器使用频率相当高,偶尔零星打扫用卧式的实在麻烦(汪星人:你这分明是找借口,我掉毛是满屋子都有,铲屎君都是用卧式满屋子吸的,你...

专题|一个品牌一件产品(英国篇)之Quested(罗杰之声)

Quested(罗杰之声)代表产品:Q212FS品牌介绍Quested(罗杰之声)是录音监听领域的传奇品牌,由英国录音师RogerQuested于1985年创立。在成立Quested之前,Roger...

常用半导体中英对照表(建议收藏)(半导体英文术语)

作为一个源自国外的技术,半导体产业涉及许多英文术语。加之从业者很多都有海外经历或习惯于用英文表达相关技术和工艺节点,这就导致许多英文术语翻译成中文后,仍有不少人照应不上或不知如何翻译。为此,我们整理了...

Fyne Audio F502SP 2.5音路低音反射式落地音箱评测

FyneAudio的F500系列,有新成员了!不过,新成员不是新的款式,却是根据原有款式提出特别版。特别版产品在原有型号后标注了SP字样,意思是SpecialProduction。Fyne一共推出...

有哪些免费的内存数据库(In-Memory Database)

以下是一些常见的免费的内存数据库:1.Redis:Redis是一个开源的内存数据库,它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合。Redis提供了快速的读写操作,并且支持持久化数据到磁...

RazorSQL Mac版(SQL数据库查询工具)

RazorSQLMac特别版是一款看似简单实则功能非常出色的SQL数据库查询、编辑、浏览和管理工具。RazorSQLformac特别版可以帮你管理多个数据库,支持主流的30多种数据库,包括Ca...

取消回复欢迎 发表评论: