锁机制
1、Synchronized是Java中的关键字,用于实现线程同步,保证多线程环境下共享资源的原子性、可见性和有序性(避免指令重排)。
- 作用:防止多个线程同时访问共享资源时,导致数据不一致的问题(如脏读、竟态条件)。
- 可修饰:实例方式(锁当前对象)、静态方法(锁类对象)、代码块(锁指定对象,格式:Synchronized(锁对象){…})。
- 不可中断性:一旦线程获取锁,其他线程只能阻塞等待,无法被中断(直到锁释放)。
2、Lock是Java5引入的接口(位于java.util.concurrent.locks)包,用于线程同步,需要手动获取和释放锁,常见实例有:ReentrantLock(可重入锁)和ReenrantReadWriteLock(读写锁)。
- 本质区别:
- Synchronized是Java语法层面的关键字,由JVM自动管理锁定释放和获取(代码块执行完成或异常 自动释放)
- Lock是API层面的接口,需要手动调用lock()获取锁,通过unlock()释放锁(通常在finally中确保释放,避免死锁)
3、synchronized和Lock的核心区别:(高频)
| 特点 | synchronized | Lock(如ReentrantLock) |
|---|---|---|
| 实现方式 | JVM内置关键字(C++实现) | 基于AQS的Java类(API层面实现) |
| 锁释放 | 自动释放(代码执行完毕或异常) | 需要手动释放(finally中调用unlock) |
| 锁类型 | 非公平锁(默认),不可设置为公平锁 | 可通过构造函数设置 公平/非公平锁 |
| 功能扩展 | 基础同步功能 | 支持中断(lockInterruptibly(),超时获取(trylock(time)),条件变量(condition)) |
| 性能 | JDK1.6后优化(偏向锁、轻量级锁),与Lock接近 | 高并发下性能更优,灵活度高 |
| 条件变量 | 仅支持一个条件队列,通过wait()/notify() | 支持多个条件队列(通过Condition) |
| 锁状态查询 | 不支持查询锁状态 | 可通过isLocked() |
4、Synchronized和Lock在“公平锁”上的区别:
- Synchronized默认不支持 公平锁,且无法设置为公平锁。线程获取锁的顺序不固定(可能出现插队现象,提高吞吐量,但可能导致线程饥饿)。
- Lock:以ReentrantLock为例,可以通过构造函数设置为公平锁。公平锁会按照线程请求锁的顺序分配(先到先得)。但会导致吞吐量减少(需要维护等待队列)。
底层实现
Synchronized的底层实现依赖于Java对象头和Monitor(监视器锁)。
- 对象头:Java对象在内存中的布局包括对象头,其中Mark Word字段存储对象的锁状态(无锁、偏向锁、轻量锁、重量锁)。
- 锁升级过程:
- 无锁状态:对象刚创建时,Mark Word存储哈希码等信息。
- 偏向锁:同一线程**多次**获取锁,直接标记为线程ID,避免CAS操作(适合单线程重复访问)。
- 轻量级锁:多线程竞争时,偏向锁升级到轻量锁,通过CAS获取锁(自旋、避免阻塞)。
- 重量级锁:**自旋失败后**,升级为重量级锁,通过**操作系统的互斥量(Mutex)**实现。线程会进入阻塞状态(开销大)。
- Monitor:重量级锁时,对象头的Mark Word指向一个Monitor对象,Monitor内部维护“等待队列”和“entry列表”,控制线程的阻塞和唤醒。
Lock(以ReentrantLock为例)的底层实现:
ReentrantLock基于AQS(AbstractQueueSynchronized,抽象队列同步器)实现:
- AQS核心是一个Volitile int state(表示锁状态,0-未锁定,>0为已锁定,支持重入时累加)和一个双向链表(存储等待锁的线程)。
- 线程获取锁时,通过CAS尝试修改state;成功则获取锁。失败则加入等待队列,进入阻塞队列。
- 释放锁时,修改state并唤醒队列中的线程,使其重新竞争锁。
使用与场景题
使用Lock实现同步?必须在finally中释放锁。
Lock lock = new ReentrantLock();
try {
lock.lock(); // 获取锁
// 临界区代码(操作共享资源)
} finally {
lock.unlock(); // 释放锁,必须在finally中,确保异常时也能释放
}
- 原因:如果临界区代码报错,lock()获取的锁 若不释放,会导致其他线程永久阻塞导致死锁。finally保证无论是否异常,锁都会释放。
Lock的tryLock()方法有什么作用?怎么避免死锁。
- trylock():尝试获取锁,返回Boolean,不会阻塞线程(可指定超时时间)
- 避免死锁:多线程获取多个锁时,用tryLock()尝试获取,失败则释放以获取的锁,重试或放弃。
例如
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
// 线程1尝试获取lock1和lock2
boolean gotLock1 = lock1.tryLock();
if (gotLock1) {
try {
boolean gotLock2 = lock2.tryLock(1, TimeUnit.SECONDS);
if (gotLock2) {
try {
// 操作共享资源
} finally {
lock2.unlock();
}
} else {
// 获取lock2失败,释放lock1
lock1.unlock();
}
} finally {
if (gotLock1) lock1.unlock();
}
}
Synchronized的wait()、notify()和Lock的Condition有什么区别。
- synchronized 依赖 Object 的 wait()、notify()、notifyAll() 实现线程间通信,仅支持一个条件队列(所有等待线程在同一队列)。
- Lock 通过 Condition 实现通信,可创建多个条件队列(每个 Condition 对应一个队列),能精确唤醒特定条件的线程。
Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition(); // 非空条件
Condition notFull = lock.newCondition(); // 非满条件
// 线程1:等待“非空”
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 进入notEmpty队列等待
}
} finally {
lock.unlock();
}
// 线程2:唤醒“非空”等待的线程
lock.lock();
try {
queue.add(element);
notEmpty.signal(); // 唤醒notEmpty队列中的一个线程
} finally {
lock.unlock();
}
Synchronized和Lock的使用场景:
- 优先使用Synchronized:
- 场景简单(如单一路径的同步)。功能不复杂(如中断、公平锁)。
- 代码简洁,无需手动释放锁,减少出错。
- 用Lock场景:
- 需要中断等待锁的线程(LockInterruptibly())。
- 需要尝试获取锁trylock(),避免死锁。
- 需要公平锁
- 需要多个条件队列(Condition)
- 竞争激烈的场景(性能更优)
其他同步方式
- volatile 关键字:
保证变量的可见性(一个线程修改后,其他线程立即可见)和禁止指令重排序,但不保证原子性(不能替代锁,适合 “单写多读” 场景)。 - 原子类:
如 AtomicInteger、AtomicReference 等,基于 CAS(Compare-And-Swap)操作实现无锁同步,适合简单的计数、状态更新场景(避免锁的开销)。 - 线程封闭:
将共享资源限制在单个线程内(如局部变量、ThreadLocal),从根本上避免同步问题(ThreadLocal 可实现 “线程私有” 变量)。
常见问题
1、死锁的产生和避免
死锁是指多个线程互相持有对方需要的锁,导致永久阻塞。避免方式:
- 按固定顺序获取锁;
- 避免长时间持有锁;
- 使用 tryLock(timeout) 超时释放;
- 用工具(如 jstack)诊断死锁。
2、锁的优化策略
JVM 对 synchronized 的优化包括:
- 锁消除:删除不可能存在竞争的锁(如局部变量的同步);
- 锁粗化:将连续的细粒度锁合并为一个粗粒度锁(减少锁开销);
- 锁升级:从偏向锁 → 轻量级锁 → 重量级锁(根据竞争程度动态升级)。
3、选择同步方式的原则
- 简单场景用 synchronized(无需手动管理锁,不易出错);
- 复杂场景(如中断、超时、公平锁、多条件)用 Lock;
- 简单计数 / 状态更新用原子类(无锁,效率高)。
1363

被折叠的 条评论
为什么被折叠?



