外观
两个线程访问一个资源遇到加锁情况,没抢到锁的线程会进入什么状态
⭐ 题目日期:
美团 - 2024/12/23
📝 题解:
当两个线程访问一个共享资源并发生锁竞争时,未抢到锁的线程会进入阻塞状态(Blocked),具体行为和状态转换如下:
1. 阻塞状态(Blocked)
- 触发条件:线程尝试获取一个已被其他线程持有的锁(如
synchronized
关键字或ReentrantLock
的显式锁)。 - 行为表现:
- 线程会暂停执行,释放CPU资源,不再参与线程调度。
- 进入锁的等待队列,直到锁被释放后重新竞争。
- 状态转换:
- Blocked → Runnable:当持有锁的线程释放锁时,等待的线程会被唤醒,重新尝试获取锁。若成功获取锁,则进入就绪状态(Runnable),等待CPU调度。
2. 不同锁机制下的细节
隐式锁(如
synchronized
):- 线程未抢到锁时,直接进入
Blocked
状态。 - 锁释放后,所有等待线程会非公平竞争(不保证先到先得)。
- 线程未抢到锁时,直接进入
显式锁(如
ReentrantLock
):- 若使用
lock()
方法未抢到锁,线程会进入等待队列,但Java线程状态可能显示为WAITING
或TIMED_WAITING
(取决于是否设置超时)。 - 可通过公平锁(
fair=true
)实现公平竞争(按等待顺序分配锁)。
- 若使用
3. 关键区别:Blocked vs. Waiting
- Blocked:因竞争锁失败而阻塞(如
synchronized
)。 - Waiting:因主动调用
Object.wait()
、LockSupport.park()
等方法进入等待状态,需外部唤醒。 - Timed Waiting:因调用带有超时的方法(如
Thread.sleep()
或Object.wait(timeout)
)进入有限时等待。
4. 线程阻塞的底层机制
- 操作系统挂起:线程会被操作系统挂起,不占用CPU资源。
- 唤醒机制:当锁释放时,操作系统会通知等待线程,将其移回就绪队列。
5. 代码示例(Java)
public class LockExample {
private final Object lock = new Object();
public void accessResource() {
synchronized (lock) { // 线程尝试获取锁
// 临界区代码
}
}
}
- 若线程A持有锁,线程B调用
accessResource()
时会进入Blocked
状态,直到线程A释放锁。
6. 注意事项
- 死锁风险:多个线程互相等待对方释放锁会导致死锁,需避免循环依赖。
- 性能优化:尽量减少锁的持有时间,使用非阻塞算法(如
CAS
)或读写锁(ReadWriteLock
)。 - 中断支持:显式锁(如
ReentrantLock
)支持通过lockInterruptibly()
响应中断,而synchronized
不直接支持。
总结
未抢到锁的线程会进入阻塞状态(Blocked),由操作系统管理其唤醒。合理使用锁机制和优化同步策略是避免线程阻塞过度、提升并发性能的关键。