上一篇
如何在Java中使用锁?
- 后端开发
- 2025-06-22
- 4090
在Java中通过synchronized关键字或Lock接口实现线程同步,synchronized可修饰方法或代码块,自动管理锁;Lock需显式调用lock()/unlock(),提供更灵活的加锁机制,如尝试获取锁、超时控制等,确保多线程安全访问共享资源。
为什么需要锁?
当多个线程同时读写共享数据(如静态变量、对象属性)时,可能引发:
- 数据竞争:线程A修改数据时,线程B同时读取导致脏数据。
- 状态不一致:非原子操作(如
i++
包含读-改-写三步)被线程交替执行。
锁通过互斥性确保同一时刻仅一个线程访问资源,并保证修改后的值对其他线程可见。
内置锁:synchronized
synchronized
是Java最基础的锁机制,JVM自动管理锁的获取与释放。
同步代码块
public class Counter { private int count = 0; private final Object lock = new Object(); // 锁对象 public void increment() { synchronized (lock) { // 对lock对象加锁 count++; // 临界区代码 } } }
- 锁对象:可以是任意对象(如
lock
),推荐使用私有final对象避免外部干扰。 - 作用范围:仅包裹需要互斥的代码(临界区),减少锁竞争。
同步方法
public synchronized void increment() { count++; // 等同于锁住当前对象实例(this) } public static synchronized void staticMethod() { // 锁住当前类的Class对象(Counter.class) }
- 实例方法:锁对象是当前实例(
this
)。 - 静态方法:锁对象是类的Class对象(如
Counter.class
)。
显式锁:Lock接口
java.util.concurrent.locks.Lock
提供更灵活的锁控制,需手动释放锁,常用实现类为ReentrantLock
。
基础用法
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SafeCounter { private final Lock lock = new ReentrantLock(); // 创建显式锁 private int value = 0; public void add(int num) { lock.lock(); // 获取锁 try { value += num; // 临界区 } finally { lock.unlock(); // 必须在finally中释放锁 } } }
- 优势:支持尝试获取锁、超时锁、可中断锁等高级功能。
- 注意:
unlock()
必须放在finally
块中,防止异常导致锁未释放。
高级功能
-
尝试锁(tryLock):避免线程阻塞
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试等待1秒 try { /* 操作资源 */ } finally { lock.unlock(); } } else { System.out.println("获取锁失败,执行备选逻辑"); }
-
可中断锁:响应线程中断
public void interruptibleLock() throws InterruptedException { lock.lockInterruptibly(); // 若线程被中断,抛出InterruptedException try { /* ... */ } finally { lock.unlock(); } }
-
公平锁:减少线程饥饿
Lock fairLock = new ReentrantLock(true); // true表示公平锁
公平锁按请求顺序分配锁,但可能降低吞吐量(默认非公平锁)。
锁的选择策略
场景 | 推荐锁类型 | 理由 |
---|---|---|
简单同步需求(如单方法互斥) | synchronized |
代码简洁,JVM自动优化 |
需要超时/尝试锁、可中断锁 | ReentrantLock |
灵活控制锁行为 |
高并发且锁竞争激烈 | ReentrantLock |
结合tryLock 可避免线程阻塞 |
读多写少场景 | ReentrantReadWriteLock |
读写分离提升并发性(非本文重点) |
避坑指南
-
死锁预防:
- 避免嵌套锁:线程A持有锁1请求锁2,线程B持有锁2请求锁1。
- 统一加锁顺序:所有线程按固定顺序获取锁(如先锁A再锁B)。
- 使用
tryLock
设置超时。
-
锁粒度优化:
- 细化锁:锁住最小必要代码块(如
synchronized
块优于同步方法)。 - 分离锁:对无关联资源使用不同锁对象(如
lockForData
和lockForLog
)。
- 细化锁:锁住最小必要代码块(如
-
性能考量:
- 避免在锁中执行耗时操作(如IO操作)。
- 优先使用线程安全类(如
ConcurrentHashMap
代替synchronized
+HashMap
)。
- 基础场景:优先用
synchronized
,简洁安全。 - 复杂需求:选择
ReentrantLock
,利用其超时、中断等特性增强健壮性。 - 核心原则:锁范围最小化、避免嵌套、及时释放。
引用说明: 参考Oracle官方文档Java Concurrency、Brian Goetz所著《Java并发编程实战》(Java Concurrency in Practice),以及Java API文档Lock接口,实践代码基于JDK 8+验证。