上一篇学习了为什么需要Lock接口,相比synchronized关键字Lock接口具有更灵活的锁控制能力,本文来进一步学习Lock接口的相关方法。
Lock核心API详解
Lock接口是JDK1.5之后引入的并发工具,声明的方法并不多,只有6个,是对锁的主要特性(上锁和解锁)的核心抽象。
1、lock()
/**
* Acquires the lock.
*
* <p>If the lock is not available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until the
* lock has been acquired.
*
* <p><b>Implementation Considerations</b>
*
* <p>A {@code Lock} implementation may be able to detect erroneous use
* of the lock, such as an invocation that would cause deadlock, and
* may throw an (unchecked) exception in such circumstances. The
* circumstances and the exception type must be documented by that
* {@code Lock} implementation.
*/
void lock();
线程调用该方法将获取锁,若锁不可用,则出于线程调度目的,当前线程将被禁止执行并进入休眠状态,直至成功获取锁。实现注意事项:Lock接口的具体实现类可检测锁的错误使用(如可能导致死锁的调用),并在该场景下抛出(非受检)异常。具体场景及异常类型必须由实现类说明。
2、lockInterruptibly()
/**
* Acquires the lock unless the current thread is
* {@linkplain Thread#interrupt interrupted}.
...
* @throws InterruptedException if the current thread is
* interrupted while acquiring the lock (and interruption
* of lock acquisition is supported)
*/
void lockInterruptibly() throws InterruptedException;
调用该方法将获取锁,和lock()方法最大的区别是支持线程中断,在阻塞期间响应中断,避免死等。
3、tryLock()
/**
* Acquires the lock only if it is free at the time of invocation.
*
* <p>Acquires the lock if it is available and returns immediately
* with the value {@code true}.
* If the lock is not available then this method will return
* immediately with the value {@code false}.
*
* <p>A typical usage idiom for this method would be:
* <pre> {@code
* Lock lock = ...;
* if (lock.tryLock()) {
* try {
* // manipulate protected state
* } finally {
* lock.unlock();
* }
* } else {
* // perform alternative actions
* }}</pre>
*
* This usage ensures that the lock is unlocked if it was acquired, and
* doesn't try to unlock if the lock was not acquired.
*
* @return {@code true} if the lock was acquired and
* {@code false} otherwise
*/
boolean tryLock();
仅当调用时锁处于空闲状态才获取锁。若锁可用则立即获取,返回true;若锁不可用则立即返回false。典型使用模式:
Lock lock = ...;
if (lock.tryLock()) {
try {
//操作受保护资源
} finally {
lock.unlock();
}
} else {
//执行备选逻辑
}
此模式确保:若获取锁则必释放,未获取锁则不尝试释放。返回true 表示成功获取锁,否则返回false。可尝试获取锁,这也是和synchronized关键字的区别之一,synchronized是不可尝试获取锁的。和lock() 方法最大的区别是立即返回获锁结果,适用于快速失败场景。
4、tryLock(long time, TimeUnit unit)
/**
* Acquires the lock if it is free within the given waiting time and the
* current thread has not been {@linkplain Thread#interrupt interrupted}.
*
* <p>If the lock is available this method returns immediately
* with the value {@code true}.
* If the lock is not available then
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until one of three things happens:
* <ul>
* <li>The lock is acquired by the current thread; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the
* current thread, and interruption of lock acquisition is supported; or
* <li>The specified waiting time elapses
* </ul>
*
* <p>If the lock is acquired then the value {@code true} is returned.
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while acquiring
* the lock, and interruption of lock acquisition is supported,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared.
*
* <p>If the specified waiting time elapses then the value {@code false}
* is returned.
* If the time is
* less than or equal to zero, the method will not wait at all.
*
* <p><b>Implementation Considerations</b>
*
* <p>The ability to interrupt a lock acquisition in some implementations
* may not be possible, and if possible may
* be an expensive operation.
* The programmer should be aware that this may be the case. An
* implementation should document when this is the case.
*
* <p>An implementation can favor responding to an interrupt over normal
* method return, or reporting a timeout.
*
* <p>A {@code Lock} implementation may be able to detect
* erroneous use of the lock, such as an invocation that would cause
* deadlock, and may throw an (unchecked) exception in such circumstances.
* The circumstances and the exception type must be documented by that
* {@code Lock} implementation.
*
* @param time the maximum time to wait for the lock
* @param unit the time unit of the {@code time} argument
* @return {@code true} if the lock was acquired and {@code false}
* if the waiting time elapsed before the lock was acquired
*
* @throws InterruptedException if the current thread is interrupted
* while acquiring the lock (and interruption of lock
* acquisition is supported)
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
调用该方法将在给定等待时间内尝试获取锁(支持线程中断响应)。若锁空闲则立即获取,返回true;若锁不可用,则出于线程调度目的,当前线程将被禁止执行并进入休眠状态,直至以下情形之一发生:
(1)当前线程成功获取锁;
(2)其他线程中断当前线程且锁实现支持获取过程的中断;
(3)超过指定等待时间。
若当前线程:
(1)进入此方法时已被中断;
(2)在获取锁过程中被中断且支持中断响应;
则抛出被中断异常InterruptedException并清除中断状态。若超过指定等待时间则返回false。若等待时间≤0,则此方法将完全不等待。实现注意事项同lock()方法。
参数与返回值说明:
time指定等待锁的最大时间;
unit 指定时间参数的单位;
返回true表示成功获取锁,返回false表示等待超时。
5、unlock()
/**
* Releases the lock.
*
* <p><b>Implementation Considerations</b>
*
* <p>A {@code Lock} implementation will usually impose
* restrictions on which thread can release a lock (typically only the
* holder of the lock can release it) and may throw
* an (unchecked) exception if the restriction is violated.
* Any restrictions and the exception
* type must be documented by that {@code Lock} implementation.
*/
void unlock();
调用该方法线程将释放锁,此方法必须有持有锁的线程调用。实现注意事项:Lock的实现通常会对释放锁的线程施加限制,典型情况下仅锁持有者能释放锁),若违反限制可能抛出(非受检)异常。具体限制及异常类型必须由实现类说明。
6、newCondition()
/**
* Returns a new {@link Condition} instance that is bound to this
* {@code Lock} instance.
*
* <p>Before waiting on the condition the lock must be held by the
* current thread.
* A call to {@link Condition#await()} will atomically release the lock
* before waiting and re-acquire the lock before the wait returns.
*
* <p><b>Implementation Considerations</b>
*
* <p>The exact operation of the {@link Condition} instance depends on
* the {@code Lock} implementation and must be documented by that
* implementation.
*
* @return A new {@link Condition} instance for this {@code Lock} instance
* @throws UnsupportedOperationException if this {@code Lock}
* implementation does not support conditions
*/
Condition newCondition();
调用该方法将返回一个Condition类型的条件实例,该实例绑定到Lock实例中。在条件上等待前,当前线程必须持有该锁。调用 Condition的await() 将在等待前原子性地释放锁,并在等待返回前重新获取锁。实现注意事项:Condition实例的具体行为取决于Lock实现,必须由实现类说明。
返回值说明:返回此锁实例的新条件对象。
异常说明:若此锁实现不支持条件变量将抛出UnsupportedOperationException 。
Lock优势举例
以下是Lock接口的典型使用场景及相比synchronized的优势说明:
1、可中断锁获取
synchronized阻塞时无法中断,Lock可通过lockInterruptibly()实现:
public boolean trySendMessage(Lock lock, Message msg) {
try {
//尝试获取锁,最多等待3秒
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
return send(msg);
} finally {
//解锁
lock.unlock();
}
}
//捕获中断异常
} catch (InterruptedException e) {
//中断当前线程
Thread.currentThread().interrupt();
}
// 超时处理
return false;
}
当线程在synchronized块内阻塞时,调用interrupt()无效,而Lock允许中断等待线程。
2、尝试非阻塞获取锁
上面的例子中,通过tryLock()方法实现快速失败机制,超过指定时间还没有获取到锁则立即返回。可以有效避免无限死等待的问题,而synchronized无法实现这种"尝试获取锁,失败立即返回"的逻辑。
3、锁等待超时
上面的例子中tryLock()方法可以指定获取锁的等待时间,避免无限等待,而synchronized无法设置获取锁的超时时间。
4、公平的抢锁实现
公平锁的实现由Lock接口的具体实现类来实现,比如ReentrantLock,可以通过构造函数的fair参数指定是否创建公平锁,创建公平锁后,抢锁的线程必须安装先来先抢的原则排队抢锁,可以有效避免线程长时间抢不到锁的问题,而synchronized仅支持非公平锁,可能导致线程饥饿。
public class FairService {
// 关键点:创建一个公平锁
private Lock fairLock = new ReentrantLock(true);
public void service() {
fairLock.lock();
try {
// 先请求锁的线程先获得资源
} finally {
fairLock.unlock();
}
}
}
5、多等待条件
在系列文章的第二篇中,我们知道synchronized与wait()/notifyAll()方法实现的多线程并发协同,无法区分不同的等待条件,也不能唤醒指定的等待线程。而单个Lock对象可以通过调用newCondition()方法创建多个Condition等等条件实现更精细通知与唤醒。
public class BoundedBuffer {
final Lock lock = new ReentrantLock();
//队列未满条件1
final Condition notFull = lock.newCondition();
//队列非空条件2
final Condition notEmpty = lock.newCondition();
public void put(Object x) throws InterruptedException {
lock.lock();
try {
// 等待非满条件
while (isFull()) notFull.await();
// 入队操作
...
// 唤醒非空条件
notEmpty.signal();
} finally {
lock.unlock();
}
}
}
核心方法与关键优势总结
核心方法总结
核心方法 | 功能描述 | 关键特性 | 适用场景 |
---|---|---|---|
void lock() | 阻塞式获取锁 | 若锁被占用则线程阻塞,直到获取锁为止 | 必须确保获取锁的场景 |
boolean tryLock() | 尝试非阻塞获取锁 | 立即返回获取结果(成功true/失败false) | 快速失败机制、避免死锁 |
boolean tryLock(long time, TimeUnit unit) | 超时尝试获取锁 | 在指定时间内等待锁,超时返回false | 带超时控制的资源竞争 |
void lockInterruptibly() | 可中断式获取锁 | 等待过程中响应线程中断请求 | 需要中断响应的长时间等待 |
void unlock() | 释放锁 | 必须由锁持有者调用,否则抛异常 | 必须配合finally块使用 |
Condition newCondition() | 创建条件变量 | 实现多等待队列(如生产者-消费者) | 精细化的线程通信 |
关键优势总结
(1)响应中断能力(避免死锁)
(2)非阻塞尝试机制(提升系统响应)
(3)公平锁支持(解决线程饥饿)
(4)多条件变量(精细线程通信)
(5)超时等待控制(系统健壮性保障)
这些特性使得Lock在某些更复杂的场景中成为不可替代的选择。