Lock
实现提供比使用synchronized
方法和语句更广泛的锁定操作。它们允许更灵活的结构化,可能具有完全不同的属性,并且可能支持多个关联的Condition
对象。
锁是控制多个线程对共享资源访问的工具。通常,锁提供对共享资源的独占访问:一次只有一个线程可以获取锁,并且所有对共享资源的访问都要求首先获取锁。然而,一些锁可能允许对共享资源的并发访问,例如ReadWriteLock
的读锁。
使用synchronized
方法或语句提供对与每个对象关联的隐式监视器锁的访问,但是强制所有锁的获取和释放以块结构方式发生:当获取多个锁时,它们必须以相反的顺序释放,并且所有锁必须在获取它们的同一词法范围内释放。
虽然synchronized
方法和语句的作用域机制使得使用监视器锁更容易,有助于避免涉及锁的许多常见编程错误,但有时您需要以更灵活的方式使用锁。例如,一些用于遍历并发访问的数据结构的算法需要使用“递进锁定”或“链锁定”:您获取节点A的锁,然后节点B的锁,然后释放A并获取C,然后释放B并获取D,依此类推。 Lock
接口的实现通过允许在不同范围内获取和释放锁,并允许以任何顺序获取和释放多个锁,从而使得可以使用这种技术。
这种增加的灵活性带来了额外的责任。不具有块结构锁定会导致synchronized
方法和语句中自动释放锁的缺失。在大多数情况下,应使用以下习语:
Lock l = ...;
l.lock();
try {
// 访问由此锁保护的资源
} finally {
l.unlock();
}
当锁定和解锁发生在不同的范围内时,必须注意确保在持有锁时执行的所有代码都受到try-finally或try-catch的保护,以确保在必要时释放锁。
Lock
实现通过提供尝试非阻塞获取锁(tryLock()
)、可以中断的获取锁(lockInterruptibly()
)和可以超时的获取锁(tryLock(long, TimeUnit)
)等功能,提供了比使用synchronized
方法和语句更多的功能。
Lock
类还可以提供与隐式监视器锁完全不同的行为和语义,例如保证排序、非可重入使用或死锁检测。如果实现提供了这种专门的语义,那么实现必须记录这些语义。
请注意,Lock
实例只是普通对象,它们本身可以作为synchronized
语句中的目标使用。获取Lock
实例的监视器锁与调用该实例的任何lock()
方法之间没有指定的关系。建议避免混淆,除了在其自己的实现中使用Lock
实例外,永远不要以这种方式使用Lock
实例。
除非另有说明,否则将任何参数传递为null
值将导致抛出NullPointerException
。
内存同步
所有Lock
实现必须强制执行与内置监视器锁提供的相同的内存同步语义,如《Java语言规范》第17章所述:
- 成功的
lock
操作具有与成功的Lock操作相同的内存同步效果。 - 成功的
unlock
操作具有与成功的Unlock操作相同的内存同步效果。
实现注意事项
锁获取的三种形式(可中断、不可中断和定时)可能在性能特征、排序保证或其他实现特性上有所不同。此外,在给定的Lock
类中,可能无法中断对锁的进行中获取。因此,实现不需要为所有三种锁获取形式定义完全相同的保证或语义,也不需要支持对进行中的锁获取进行中断。实现必须清楚地记录每个锁定方法提供的语义和保证。它还必须遵守此接口中定义的中断语义,以支持锁获取的中断:这要么是完全支持,要么仅在方法进入时支持。
由于中断通常意味着取消,并且对中断的检查通常不频繁,因此实现可以更倾向于响应中断而不是正常方法返回。即使可以证明中断发生在另一个操作可能解除线程阻塞之后,实现也应记录此行为。
- 参见Java语言规范:
-
17.4 内存模型
- 自JDK版本:
- 1.5
- 参见:
-
Method Summary
-
Method Details
-
lock
void lock()获取锁。如果锁不可用,则当前线程将因线程调度目的而被禁用,并处于休眠状态,直到获取锁为止。
实现注意事项
Lock
实现可能能够检测锁的错误使用,例如可能导致死锁的调用,并且在这种情况下可能会抛出(未检查的)异常。这种情况和异常类型必须由该Lock
实现记录。 -
lockInterruptibly
获取锁,除非当前线程被中断。如果锁可用,则获取锁并立即返回。
如果锁不可用,则当前线程将因线程调度目的而被禁用,并处于休眠状态,直到发生以下两种情况之一:
- 当前线程获取锁;或
- 其他线程中断当前线程,并且支持锁获取的中断。
如果当前线程:
- 在进入此方法时设置了中断状态;或
- 在获取锁时被中断,并且支持锁获取的中断,
InterruptedException
,并清除当前线程的中断状态。实现注意事项
在某些实现中,可能无法中断锁获取操作,即使可能也是昂贵的操作。程序员应意识到这种情况。实现应记录这种情况。
实现可以更倾向于响应中断而不是正常方法返回。
Lock
实现可能能够检测锁的错误使用,例如可能导致死锁的调用,并且在这种情况下可能会抛出(未检查的)异常。这种情况和异常类型必须由该Lock
实现记录。- 抛出:
-
InterruptedException
- 如果当前线程在获取锁时被中断(并且支持锁获取的中断)
-
tryLock
boolean tryLock()仅在调用时锁是空闲状态时才获取锁。如果锁可用,则获取锁并立即返回值
true
。如果锁不可用,则此方法将立即返回值false
。此方法的典型用法习惯是:
Lock lock = ...; if (lock.tryLock()) { try { // 操作受保护的状态 } finally { lock.unlock(); } } else { // 执行替代操作 }
- 返回:
-
如果获取了锁则返回
true
,否则返回false
-
tryLock
获取锁,如果在给定的等待时间内锁是空闲的,并且当前线程尚未被中断。如果锁可用,则此方法立即返回值
true
。如果锁不可用,则当前线程变为禁用以进行线程调度,并处于休眠状态,直到发生以下三种情况之一:- 锁被当前线程获取;或
- 其他线程中断当前线程,并且支持中断锁获取;或
- 指定的等待时间过去
如果锁被获取,则返回值为
true
。如果当前线程:
- 在进入此方法时已设置了中断状态;或
- 在获取锁时被中断,并且支持中断锁获取,
InterruptedException
,并清除当前线程的中断状态。如果指定的等待时间过去,则返回值为
false
。如果时间小于或等于零,则该方法将根本不等待。实现注意事项
在某些实现中,中断锁获取的能力可能是不可能的,如果可能的话,可能是一个昂贵的操作。程序员应该意识到这种情况。实现应该在这种情况下进行文档记录。
实现可以优先响应中断而不是正常方法返回,或报告超时。
Lock
实现可能能够检测锁的错误使用,例如会导致死锁的调用,并且在这种情况下可能会抛出(未经检查的)异常。这种情况和异常类型必须由该Lock
实现进行文档记录。- 参数:
-
time
- 等待锁的最长时间 -
unit
-time
参数的时间单位 - 返回:
-
如果获取了锁,则返回
true
,如果在获取锁之前等待时间已过,则返回false
- 抛出:
-
InterruptedException
- 如果当前线程在获取锁时被中断(并且支持中断锁获取)
-
unlock
void unlock()释放锁。实现注意事项
Lock
实现通常会对哪个线程可以释放锁施加限制(通常只有锁的持有者可以释放它),如果违反了限制,则可能会抛出(未经检查的)异常。任何限制和异常类型必须由该Lock
实现进行文档记录。 -
newCondition
Condition newCondition()返回绑定到此Lock
实例的新Condition
实例。在等待条件之前,当前线程必须持有锁。调用
Condition.await()
将在等待之前原子性地释放锁,并在等待返回之前重新获取锁。实现注意事项
Condition
实例的确切操作取决于Lock
实现,并必须由该实现进行文档记录。- 返回:
-
此
Lock
实例的新Condition
实例 - 抛出:
-
UnsupportedOperationException
- 如果此Lock
实现不支持条件
-