【面试专栏|Java并发编程】Java并发锁对比:synchronized与Lock,底层原理+适用场景详解
予枫个人主页 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》《Java 面试刷题指南》 Debug 这个世界Return 更好的自己引言面试Java并发synchronized与Lock接口的对比绝对是高频考点很多程序员只会用却分不清两者的底层差异、适用场景被面试官追问“什么时候用synchronized什么时候用Lock”时直接翻车。今天就从底层原理、核心区别、适用场景三个维度结合案例和面试追问帮你彻底分清两者面试不慌、实战不踩坑文章目录引言一、前言为什么要对比synchronized与Lock二、synchronized 与 Lock 核心区别底层用法面试必答2.1 底层实现差异面试官重点追问synchronized 底层实现Lock 底层实现2.2 用法差异结合代码案例一看就会synchronized 用法3种场景简单易用Lock 用法需手动释放灵活度高2.3 核心特性差异实战重点三、适用场景对比实战选型指南避免踩坑3.1 优先用 synchronized 的场景3.2 优先用 Lock 的场景3.3 选型总结图文可视化四、面试官追问环节实战必备拉开差距追问1synchronized 和 Lock 的性能对比JDK1.6后synchronized为什么性能提升这么多追问2Lock的tryLock()和lock()方法有什么区别tryLock()的优势是什么追问3ReentrantLock为什么叫可重入锁和synchronized的可重入性有什么区别追问4公平锁和非公平锁的区别synchronized和Lock默认是什么锁追问5使用Lock时为什么必须在finally块中释放锁五、总结一、前言为什么要对比synchronized与Lock在Java并发编程中synchronized和Lock都是实现线程同步、保证线程安全的核心方式但两者的设计理念、底层实现、使用场景截然不同。synchronized是Java内置关键字简单易用、自动释放锁适合大多数基础同步场景Lock是Java.util.concurrent.locks包下的接口需手动释放锁、功能更灵活适合复杂并发场景。 小提示面试时面试官不仅会问“两者有什么区别”还会追问“为什么这么设计”“实际项目中怎么选”建议点赞收藏吃透这篇轻松应对所有相关追问二、synchronized 与 Lock 核心区别底层用法面试必答两者的区别主要集中在底层实现、锁特性、用法三个维度用表格对比更清晰面试时直接按这个框架回答逻辑更清晰对比维度synchronizedLock接口以ReentrantLock为例底层实现依赖JVM的Monitor监视器锁基于对象头依赖AQSAbstractQueuedSynchronizer框架基于CAS操作锁释放自动释放代码执行完/抛出异常时自动释放手动释放必须在finally块中调用unlock()否则会造成死锁锁类型非公平锁默认不可手动指定可指定公平锁/非公平锁构造方法传入boolean值可中断性不可中断线程获取锁时会一直阻塞除非获取到锁或被中断可中断调用lockInterruptibly()方法可响应中断尝试获取锁不可尝试一旦调用必须等待锁释放可尝试获取锁tryLock()方法超时可放弃避免死锁条件变量无专门条件变量需通过wait()/notify()/notifyAll()配合有Condition接口可实现多条件唤醒精准唤醒特定线程性能JDK1.6优化后偏向锁、轻量级锁性能接近Lock高并发下略逊于Lock高并发场景下性能更优灵活度更高2.1 底层实现差异面试官重点追问synchronized 底层实现synchronized的底层依赖JVM的Monitor监视器锁关联对象头的Mark Word之前在synchronized核心原理中详细讲过这里重点对比Lock当线程获取synchronized锁时本质是获取对象对应的Monitor所有权释放锁时自动释放Monitor无需手动操作JVM会处理异常场景下的锁释放。Lock 底层实现Lock接口的核心实现类是ReentrantLock底层依赖AQS框架抽象队列同步器AQS内部维护一个volatile修饰的状态变量state用于表示锁的持有状态线程通过CAS操作修改state的值获取锁state从0变为1和释放锁state从1变为0AQS维护一个等待队列未获取到锁的线程会进入队列等待避免忙等提升性能。2.2 用法差异结合代码案例一看就会synchronized 用法3种场景简单易用// 场景1修饰普通方法锁thispublicsynchronizedvoidsyncMethod(){// 同步代码System.out.println(synchronized修饰普通方法);}// 场景2修饰静态方法锁Class对象publicstaticsynchronizedvoidsyncStaticMethod(){// 同步代码System.out.println(synchronized修饰静态方法);}// 场景3修饰代码块显式指定锁对象publicvoidsyncBlock(){ObjectlocknewObject();synchronized(lock){// 同步代码System.out.println(synchronized修饰代码块);}}✅ 优势无需手动释放锁代码简洁不易出错适合简单同步场景。Lock 用法需手动释放灵活度高以ReentrantLock最常用的Lock实现类为例importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;publicclassLockDemo{// 1. 创建Lock对象可指定公平锁true/非公平锁false默认privatefinalLocklocknewReentrantLock(true);publicvoidlockMethod(){// 2. 手动获取锁lock.lock();try{// 3. 同步代码System.out.println(Lock接口实现同步);}finally{// 4. 手动释放锁必须在finally中避免异常导致死锁lock.unlock();}}// 尝试获取锁超时放弃避免死锁publicvoidtryLockMethod()throwsInterruptedException{// 尝试获取锁超时时间3秒获取失败则放弃if(lock.tryLock(3,TimeUnit.SECONDS)){try{System.out.println(尝试获取锁成功);}finally{lock.unlock();}}else{System.out.println(尝试获取锁失败超时放弃);}}}⚠️ 注意Lock必须手动释放锁若忘记在finally中调用unlock()会导致锁无法释放造成死锁这是新手最容易踩的坑2.3 核心特性差异实战重点锁的公平性synchronized只能是非公平锁Lock可自由选择公平锁适合对顺序要求高的场景非公平锁性能更优可中断性Lock的lockInterruptibly()方法可中断线程的锁等待避免线程一直阻塞比如需要停止某个线程时synchronized无法做到条件唤醒Lock的Condition接口可实现精准唤醒比如生产者-消费者模型中可分别唤醒生产者和消费者而synchronized的notify()只能随机唤醒一个线程notifyAll()唤醒所有线程效率较低。三、适用场景对比实战选型指南避免踩坑很多人分不清两者的使用场景其实核心原则是简单场景用synchronized复杂场景用Lock具体拆解如下3.1 优先用 synchronized 的场景简单同步场景比如单线程修改共享变量、简单的方法同步代码简洁无需复杂的锁操作不需要灵活控制锁的场景无需中断锁等待、无需尝试获取锁、无需多条件唤醒synchronized自动释放锁不易出错低并发场景低并发下synchronized的性能和Lock差距不大且使用更简单开发效率更高。示例简单的计数器同步适合用synchronizedpublicclassCounter{privateintcount0;// 简单同步用synchronized更简洁publicsynchronizedvoidincrement(){count;}publicsynchronizedintgetCount(){returncount;}}3.2 优先用 Lock 的场景高并发场景高并发下Lock的性能更优AQS框架的等待队列机制比synchronized的Monitor更高效需要灵活控制锁的场景需指定公平锁/非公平锁比如秒杀场景需要公平分配锁避免线程饥饿需中断锁等待比如用户取消操作时中断线程的锁等待避免资源浪费需尝试获取锁比如超时获取锁避免线程一直阻塞造成死锁多条件唤醒场景比如生产者-消费者模型需要分别唤醒生产者和消费者用Lock的Condition接口更高效。示例生产者-消费者模型适合用Lockimportjava.util.LinkedList;importjava.util.Queue;importjava.util.concurrent.locks.Condition;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;publicclassProducerConsumer{privatefinalQueueIntegerqueuenewLinkedList();privatefinalintcapacity5;privatefinalLocklocknewReentrantLock();// 两个条件变量队列满生产者等待、队列空消费者等待privatefinalConditionnotFulllock.newCondition();privatefinalConditionnotEmptylock.newCondition();// 生产者publicvoidproduce(intvalue)throwsInterruptedException{lock.lock();try{// 队列满生产者等待while(queue.size()capacity){notFull.await();}queue.offer(value);System.out.println(生产者生产value);// 唤醒消费者队列非空notEmpty.signal();}finally{lock.unlock();}}// 消费者publicintconsume()throwsInterruptedException{lock.lock();try{// 队列空消费者等待while(queue.isEmpty()){notEmpty.await();}intvaluequeue.poll();System.out.println(消费者消费value);// 唤醒生产者队列非满notFull.signal();returnvalue;}finally{lock.unlock();}}}3.3 选型总结图文可视化简单场景无灵活锁需求、低并发复杂场景高并发、灵活锁需求优点简洁、自动释放锁、不易出错优点高性能、可控制、多条件唤醒并发同步需求场景复杂度优先用synchronized优先用Lock适合基础同步场景适合复杂并发场景 互动提示评论区说说你项目中用synchronized还是Lock遇到过哪些选型坑收藏这张流程图实战选型时直接对照不踩坑四、面试官追问环节实战必备拉开差距这部分是核心亮点比纯八股文更有实战价值提前准备好面试时直接应对追问1synchronized 和 Lock 的性能对比JDK1.6后synchronized为什么性能提升这么多性能对比JDK1.6之前synchronized是重量级锁性能远低于LockJDK1.6对synchronized进行优化引入偏向锁、轻量级锁、自旋锁等性能接近Lock高并发场景下Lock的性能略优于synchronized因为AQS的等待队列机制更高效。synchronized性能提升原因引入偏向锁、轻量级锁减少重量级锁的使用避免内核态切换引入自旋锁、自适应自旋锁减少线程阻塞实现锁消除、锁粗化减少不必要的锁操作优化Monitor的实现提升锁的获取和释放效率。追问2Lock的tryLock()和lock()方法有什么区别tryLock()的优势是什么lock()获取锁时若锁已被持有线程会一直阻塞直到获取到锁不可中断tryLock()尝试获取锁若获取成功返回true失败返回false不会阻塞可指定超时时间tryLock(long timeout, TimeUnit unit)超时后放弃获取锁。优势避免线程一直阻塞可有效防止死锁比如超时后放弃获取锁释放资源适合高并发场景下的锁竞争。追问3ReentrantLock为什么叫可重入锁和synchronized的可重入性有什么区别两者都是可重入锁同一线程可多次获取同一把锁区别在于实现方式synchronized的可重入性底层通过Monitor的计数器实现线程第一次获取锁时计数器为1再次获取计数器加1释放时计数器减1直到为0释放锁ReentrantLock的可重入性底层通过AQS的state变量实现state初始为0线程获取锁时state加1再次获取时state继续加1释放时state减1直到为0释放锁。相同点都支持可重入避免同一线程多次获取锁导致死锁。追问4公平锁和非公平锁的区别synchronized和Lock默认是什么锁区别公平锁严格按照线程等待顺序获取锁不会出现线程饥饿但性能较低需要维护等待队列的顺序非公平锁线程释放锁后新到来的线程可优先获取锁无需排队性能更高但可能导致某些线程长期无法获取锁线程饥饿。默认锁类型synchronized默认是非公平锁不可手动指定LockReentrantLock默认是非公平锁可通过构造方法传入true指定为公平锁。追问5使用Lock时为什么必须在finally块中释放锁因为Lock需要手动释放锁若同步代码块中抛出异常线程会直接退出若未在finally中释放锁锁会一直被持有导致其他线程无法获取锁造成死锁。示例若未在finally中释放锁抛出异常后锁无法释放// 错误示例易造成死锁publicvoidwrongLockMethod(){lock.lock();// 若此处抛出异常unlock()不会执行锁无法释放System.out.println(同步代码);lock.unlock();}// 正确示例必须在finally中释放publicvoidrightLockMethod(){lock.lock();try{System.out.println(同步代码);}finally{lock.unlock();// 无论是否抛出异常都会释放锁}}五、总结synchronized和Lock都是Java并发编程的核心同步方式核心区别和选型要点总结如下核心区别底层实现Monitor vs AQS、锁释放自动 vs 手动、锁特性灵活度不同、性能高并发下Lock更优选型原则简单场景用synchronized简洁、不易出错复杂场景用Lock灵活、高性能面试重点底层实现差异、性能对比、适用场景、可重入性、锁的公平性。掌握这些内容无论是面试中的对比题还是实战中的选型都能从容应对。建议结合代码练习加深理解避免死记硬背真正做到学以致用。 最后提示如果觉得这篇文章对你有帮助点赞收藏关注我予枫后续持续更新Java并发面试干货带你避开面试坑、吃透底层原理评论区留下你的面试经历或项目选型经验一起交流学习