
25 | synchronized 和 ReentrantLock 该怎么选摘要synchronized是 JVM 内置锁简单易用ReentrantLock是 Java 代码实现的锁功能更强大。本文讲清两者的区别和选型原则。一、问题现象很多人不知道该用哪个// 方式一synchronizedpublicvoidprocess(){synchronized(this){// 业务逻辑}}// 方式二ReentrantLockprivatefinalReentrantLocklocknewReentrantLock();publicvoidprocess(){lock.lock();try{// 业务逻辑}finally{lock.unlock();}}到底该用哪个二、踩坑现场场景 1需要超时获取锁synchronized 做不到// ❌ synchronized 无法设置超时会一直等publicvoidprocess(){synchronized(this){// 如果锁被占住会一直阻塞}}场景 2需要可中断的锁等待// ❌ synchronized 的等待无法被中断publicvoidprocess(){synchronized(this){// 线程在等锁的过程中无法被 interrupt() 唤醒}}场景 3需要公平锁// ❌ synchronized 不保证公平性抢锁// 某些场景下需要公平锁先到先得三、原理解析3.1 synchronized 的特点优势JVM 内置自动加锁/释放锁不需要手动unlock()JDK 1.6 之后做了大量优化偏向锁、轻量级锁、自旋锁性能已接近ReentrantLock代码简洁劣势无法设置超时无法中断等待不支持公平锁只支持独占锁不支持共享锁锁的范围只能是代码块或方法3.2 ReentrantLock 的特点优势支持超时获取锁tryLock(timeout, unit)支持可中断lockInterruptibly()支持公平锁构造时传true支持多条件变量newCondition()可以尝试获取锁tryLock()不阻塞劣势必须手动unlock()忘了就会死锁代码比synchronized长3.3 功能对比表功能synchronizedReentrantLock自动释放锁✅❌需手动超时获取锁❌✅tryLock(timeout)可中断❌✅lockInterruptibly()公平锁❌✅new ReentrantLock(true)多条件变量❌只有一个隐式条件✅newCondition()尝试获取锁不阻塞❌✅tryLock()性能JDK 1.6 已优化接近略好高并发下四、正确写法4.1 简单场景用 synchronized// ✅ 推荐简单场景用 synchronizedpublicclassCounter{privateintcount0;publicsynchronizedvoidincrement(){// 锁的是 thiscount;}publicintgetCount(){returncount;// 读也需要加锁保证可见性}}4.2 需要超时用 ReentrantLock// ✅ 推荐需要超时用 ReentrantLockpublicclassResource{privatefinalReentrantLocklocknewReentrantLock();publicvoidprocess(){try{if(lock.tryLock(5,TimeUnit.SECONDS)){// ✅ 最多等 5 秒try{// 业务逻辑}finally{lock.unlock();// ✅ 必须手动释放}}else{log.warn(获取锁超时);}}catch(InterruptedExceptione){Thread.currentThread().interrupt();// ✅ 恢复中断标志}}}4.3 需要可中断用 lockInterruptibly()// ✅ 推荐需要可中断用 lockInterruptibly()publicvoidprocess(){try{lock.lockInterruptibly();// ✅ 可以被 interrupt() 唤醒try{// 业务逻辑}finally{lock.unlock();}}catch(InterruptedExceptione){log.info(任务被中断);Thread.currentThread().interrupt();}}4.4 生产者-消费者用 Condition// ✅ 推荐需要多条件变量用 ConditionpublicclassBlockingQueueT{privatefinalListTqueuenewArrayList();privatefinalintmaxSize;privatefinalReentrantLocklocknewReentrantLock();privatefinalConditionnotEmptylock.newCondition();privatefinalConditionnotFulllock.newCondition();publicvoidput(Titem)throwsInterruptedException{lock.lock();try{while(queue.size()maxSize){notFull.await();// ✅ 等待不满条件}queue.add(item);notEmpty.signal();// ✅ 唤醒不空的等待线程}finally{lock.unlock();}}publicTtake()throwsInterruptedException{lock.lock();try{while(queue.isEmpty()){notEmpty.await();// ✅ 等待不空条件}Titemqueue.remove(0);notFull.signal();// ✅ 唤醒不满的等待线程returnitem;}finally{lock.unlock();}}}五、最佳实践✅ synchronized vs ReentrantLock 选型指南场景推荐方案原因简单的互斥synchronized代码简洁自动释放锁需要超时ReentrantLock.tryLock(timeout)synchronized不支持需要可中断ReentrantLock.lockInterruptibly()synchronized不支持生产者-消费者ReentrantLockCondition多条件变量读多写少ReentrantReadWriteLock读写分离读读不互斥 JDK 1.6 之后的 synchronized 优化JDK 1.6 对synchronized做了大量优化引入了偏向锁第一个获取锁的线程后续无需 CAS轻量级锁短时间竞争用 CAS不阻塞线程自旋锁获取锁失败时短暂自旋避免挂起线程重量级锁长时间竞争才升级为操作系统互斥量结论JDK 1.6synchronized性能已接近ReentrantLock优先用synchronized代码简洁。️ 读写分离场景用 ReadWriteLock// ✅ 读多写少场景用 ReadWriteLockprivatefinalReadWriteLockrwLocknewReentrantReadWriteLock();privatefinalLockreadLockrwLock.readLock();privatefinalLockwriteLockrwLock.writeLock();publicStringgetConfig(Stringkey){readLock.lock();// ✅ 多读并发try{returnconfig.get(key);}finally{readLock.unlock();}}publicvoidsetConfig(Stringkey,Stringvalue){writeLock.lock();// ✅ 写独享try{config.put(key,value);}finally{writeLock.unlock();}}六、小结简单互斥用synchronizedJDK 1.6 性能已够好代码简洁需要超时/可中断/公平锁/多条件变量用ReentrantLock读多写少用ReentrantReadWriteLock读读并发ReentrantLock必须手动unlock()放在finally里不确定用哪个优先synchronized需要高级功能再换ReentrantLock第三辑完。—— 下一篇进入第四辑JVM 与性能篇首篇toString里打印对象小心无限递归栈溢出。