Synchronized 与 ReentrantLock 深度对比
前言在Java并发编程中锁机制是保证线程安全的核心手段。synchronized和ReentrantLock是两种最常用的锁实现面试中经常被要求对比它们的区别。本文将深入分析两者的底层原理、功能特性、性能差异以及各自的适用场景。一、快速概览维度synchronizedReentrantLock类型关键字JVM实现API类Java实现锁获取/释放自动JVM保证释放手动必须在finally中unlock灵活性较低高支持tryLock、超时、中断公平性非公平锁默认非公平支持公平锁条件变量单一wait/notify多个Condition锁升级JDK 6后支持无底层实现对象头Mark Word 操作系统MutexAQS CAS二、Synchronized 详解2.1 使用方式// 1. 修饰实例方法锁当前实例对象publicsynchronizedvoidmethod1(){// 业务逻辑}// 2. 修饰静态方法锁当前类的Class对象publicstaticsynchronizedvoidmethod2(){// 业务逻辑}// 3. 修饰代码块锁指定对象publicvoidmethod3(){synchronized(this){// 业务逻辑}}2.2 底层原理synchronized 的锁信息存储在对象头的 Mark Word 中。JDK 6 之前的实现基于操作系统的**互斥量Mutex**实现每次加锁/解锁都需要从用户态切换到内核态开销较大被称为“重量级锁”。JDK 6 之后的锁升级机制为了减少重量级锁的开销JVM引入了锁升级机制锁状态从低到高逐步升级不可降级无锁 → 偏向锁 → 轻量级锁 → 重量级锁锁状态适用场景原理偏向锁只有一个线程重复获取锁记录线程ID无需CAS轻量级锁少量线程竞争CAS自旋尝试获取不自旋过度重量级锁多线程激烈竞争阻塞等待操作系统Mutex锁升级的好处在低竞争场景下避免了操作系统层面的线程阻塞大幅提升了性能。2.3 锁的释放synchronized 的锁释放是自动的方法执行完毕自动释放代码块执行完毕自动释放抛出异常时JVM自动释放优点不会因忘记释放锁而导致死锁。三、ReentrantLock 详解3.1 使用方式publicclassReentrantLockExample{privatefinalReentrantLocklocknewReentrantLock();publicvoiddoSomething(){lock.lock();// 获取锁try{// 业务逻辑}finally{lock.unlock();// 必须手动释放}}}3.2 核心特性① 可重入性同一个线程可以多次获取同一把锁每获取一次计数器1释放一次计数器-1计数器归零时锁才真正释放。lock.lock();lock.lock();// 可重入try{// 业务逻辑}finally{lock.unlock();lock.unlock();// 需要释放两次}② 公平锁与非公平锁// 非公平锁默认ReentrantLocknonFairLocknewReentrantLock();// 公平锁ReentrantLockfairLocknewReentrantLock(true);公平锁线程按照请求顺序获取锁保证FIFO。非公平锁允许插队吞吐量更高减少线程挂起/唤醒开销。③ 尝试获取锁tryLock// 非阻塞获取if(lock.tryLock()){try{// 获取成功}finally{lock.unlock();}}else{// 获取失败执行其他逻辑}// 带超时获取if(lock.tryLock(1,TimeUnit.SECONDS)){// ...}④ 可中断获取锁lock.lockInterruptibly();// 响应中断⑤ 条件变量ConditionclassBoundedBuffer{privatefinalReentrantLocklocknewReentrantLock();privatefinalConditionnotFulllock.newCondition();privatefinalConditionnotEmptylock.newCondition();publicvoidput(Objectitem)throwsInterruptedException{lock.lock();try{while(isFull()){notFull.await();// 等待不满}// 插入元素notEmpty.signal();// 唤醒等待非空的线程}finally{lock.unlock();}}publicObjecttake()throwsInterruptedException{lock.lock();try{while(isEmpty()){notEmpty.await();// 等待非空}// 取出元素notFull.signal();// 唤醒等待不满的线程returnitem;}finally{lock.unlock();}}}优势一个 ReentrantLock 可以创建多个 Condition实现精确唤醒而 synchronized 的wait/notify只能随机唤醒一个或全部。3.3 底层原理AQSReentrantLock 基于AQSAbstractQueuedSynchronizer实现。AQS 维护了一个volatile int state同步状态和一个FIFO 等待队列state 0锁未被占用state 0锁被占用且可重入计数非公平锁的获取流程CAS 尝试将 state 从 0 改为 1成功则获取锁如果当前线程已持有锁state 1可重入如果 CAS 失败进入等待队列排队四、深度对比4.1 功能特性对比功能synchronizedReentrantLock可重入✅✅支持中断❌✅ (lockInterruptibly)超时获取❌✅ (tryLock)公平锁❌✅多条件变量❌✅尝试非阻塞获取❌✅4.2 性能对比JDK 6 引入锁升级机制后synchronized 在低竞争场景下性能大幅提升甚至优于 ReentrantLock。低竞争场景两者性能相当synchronized 略有优势高竞争场景ReentrantLock 的灵活性如 tryLock可以避免不必要的阻塞表现更好4.3 使用场景选择优先使用 synchronized简单的同步需求方法级别或简单代码块希望代码简洁、不易出错选择 ReentrantLock需要尝试获取锁tryLock需要超时获取锁需要响应中断需要公平锁需要多条件变量Condition五、常见面试追问Q1什么是可重入锁为什么需要可重入答可重入锁指同一个线程可以多次获取同一把锁不会造成死锁。这在递归调用、嵌套同步场景中非常必要。Q2公平锁和非公平锁的区别答公平锁按请求顺序获取锁吞吐量较低避免饥饿非公平锁允许插队吞吐量更高但可能导致线程饥饿Q3synchronized 锁升级的原理答JVM 根据锁的竞争程度将锁从偏向锁 → 轻量级锁 → 重量级锁逐步升级减少在低竞争场景下操作系统的上下文切换开销。六、总结维度结论简单场景用 synchronized代码简洁、安全高级功能需求用 ReentrantLock灵活可控性能JDK 6 后两者差距不大根据场景选择面试建议回答时可以先用表格快速对比然后结合锁升级和AQS展示深度最后给出使用建议体现工程思维。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2452888.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!