Skip to content

两个线程访问一个资源遇到加锁情况,没抢到锁的线程会进入什么状态

约 693 字大约 2 分钟

多线程与并发美团

2025-03-26

⭐ 题目日期:

美团 - 2024/12/23

📝 题解:

当两个线程访问一个共享资源并发生锁竞争时,未抢到锁的线程会进入阻塞状态(Blocked),具体行为和状态转换如下:


1. 阻塞状态(Blocked)

  • 触发条件:线程尝试获取一个已被其他线程持有的锁(如synchronized关键字或ReentrantLock的显式锁)。
  • 行为表现
    • 线程会暂停执行,释放CPU资源,不再参与线程调度。
    • 进入锁的等待队列,直到锁被释放后重新竞争。
  • 状态转换
    • Blocked → Runnable:当持有锁的线程释放锁时,等待的线程会被唤醒,重新尝试获取锁。若成功获取锁,则进入就绪状态(Runnable),等待CPU调度。

2. 不同锁机制下的细节

  • 隐式锁(如synchronized

    • 线程未抢到锁时,直接进入Blocked状态。
    • 锁释放后,所有等待线程会非公平竞争(不保证先到先得)。
  • 显式锁(如ReentrantLock

    • 若使用lock()方法未抢到锁,线程会进入等待队列,但Java线程状态可能显示为WAITINGTIMED_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),由操作系统管理其唤醒。合理使用锁机制和优化同步策略是避免线程阻塞过度、提升并发性能的关键。