剖析AQS之可中断独占锁
2024-02-15 11:55:09
在上一篇文章中,我们详细探讨了AQS(AbstractQueuedSynchronizer)的核心原理,包括独占/共享模式、同步状态的管理以及等待队列的结构。本篇作为系列文章的第二部分,我们将继续深入AQS的内部机制,重点分析可中断锁和不可中断锁的实现方式以及它们之间的区别。
可中断锁与不可中断锁
在并发编程中,锁是用于保护共享资源的重要工具,它可以防止多个线程同时访问同一资源,从而避免数据不一致的问题。AQS提供了两种不同类型的锁:可中断锁和不可中断锁。它们的主要区别在于线程在获取锁的过程中是否可以被中断。
可中断锁: 当一个线程尝试获取一个可中断锁时,如果锁已经被其他线程持有,那么该线程会被阻塞并进入等待队列。如果在等待过程中,该线程被其他线程中断(例如通过调用Thread.interrupt()
方法),那么该线程会立即退出等待状态,并抛出InterruptedException
异常。这意味着线程可以响应中断请求,而不是无限期地等待下去。
不可中断锁: 与可中断锁不同,当一个线程尝试获取一个不可中断锁时,即使它被其他线程中断,它也不会退出等待状态。线程会一直阻塞,直到成功获取到锁或者发生其他异常。
AQS 中的实现
AQS 通过 acquireInterruptibly()
方法和 acquire()
方法分别实现了可中断锁和不可中断锁的获取逻辑。这两个方法都依赖于 tryAcquire()
方法来尝试获取锁,如果获取失败,则会将当前线程封装成一个节点并加入到等待队列中。
acquireInterruptibly()
方法: 该方法在尝试获取锁失败后,会检查线程的中断状态。如果线程已经被中断,则会抛出 InterruptedException
异常并退出等待;否则,线程会继续等待,直到获取到锁或者被中断。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
acquire()
方法: 该方法在尝试获取锁失败后,不会检查线程的中断状态,而是直接将线程加入等待队列并阻塞,直到获取到锁或者发生其他异常。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
应用场景
可中断锁和不可中断锁适用于不同的场景。
可中断锁: 适用于需要响应中断请求的场景,例如:
- 当一个线程需要执行一个长时间的任务,并且需要能够及时响应取消请求时,可以使用可中断锁。
- 当多个线程需要竞争同一个资源,并且需要避免某个线程长时间占用资源导致其他线程无法获取时,可以使用可中断锁。
不可中断锁: 适用于对中断不敏感的场景,例如:
- 当一个线程需要执行一个关键任务,并且不能被中断时,可以使用不可中断锁。
- 当多个线程需要协同工作,并且需要保证某个线程的操作不会被其他线程中断时,可以使用不可中断锁。
常见问题及解答
1. 可中断锁和不可中断锁的区别是什么?
答:可中断锁允许线程在获取锁的过程中被中断,而不可中断锁则不允许。
2. 什么时候应该使用可中断锁?
答:当需要响应中断请求,或者需要避免线程长时间阻塞时,可以使用可中断锁。
3. 什么时候应该使用不可中断锁?
答:当需要保证线程的操作不会被中断,或者需要执行关键任务时,可以使用不可中断锁。
4. 如果一个线程在获取不可中断锁的过程中被中断会发生什么?
答:线程会继续等待,直到获取到锁或者发生其他异常。
5. AQS 如何实现可中断锁和不可中断锁?
答:AQS 通过 acquireInterruptibly()
方法和 acquire()
方法分别实现了可中断锁和不可中断锁的获取逻辑。
总而言之,可中断锁和不可中断锁是 AQS 提供的两种重要的同步机制,它们各有优缺点,开发者需要根据具体的应用场景选择合适的锁类型。理解它们的工作原理和区别,对于编写高效、可靠的并发程序至关重要。