Lock与ReentrantLock
与内置加锁机制不同的是,Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁的方法都是显式的。Lock接口代码如下:
public interface Lock{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock实现Lock接口,并提供令人与synchronized相同的互斥性和内存可见性。与synchronized相比,它还为处理锁的不可用性问题提供了更高的灵活性。
以下代码给出了Lock接口的标准使用形式,这种形式比使用内置锁复杂一些,必须在finally块中释放锁。否则,如果在被保护的代码块中抛出了异常,那么这个锁永远都无法释放。当使用加锁时,还须考虑在try块中抛出异常的情况,如果可能使对象处于某种不一致的状态,那么就需要更多的try-catch或try-finally代码块。
Lock lock = new ReentrantLock();
...
lock.lock();
try{
//更新对象状态
//捕获异常,并在必要时恢复不变性条件
}finally{
lock.unlock();
}
轮询锁与定时锁
可定时的与可轮询的锁获取模式是由tryLock方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。在内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重新启动程序,而防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。可定时与可轮询的锁提供了另一种选择:避免死锁的发生。
以下代码通过tryLock来获取两个锁,如果不能同时获得,那么就回退并重新尝试。
while(true){
if(fromAcct.lock.tryLock()){
try{
if(toAcct.lock.tryLock(){
try{
...
}finally{
toAcct.lock.unLock();
}
}
}finally{
fromAcct.lock.unlock();
}
}
}
Condition 对象
Condition是一种广义的内置条件队列,代码如下:
public interface Condition{
void await() throws InterruptedExcaption;
boolean await(long time, TimeUnit unit) throws InterruptedException;
long awaitNanos(long nanosTimeout) throws InterruptedException;
void awaitUninterruptibly();
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
每个内置锁都只能有一个相关联的条件队列,因而在想BoundedBuffer这种类中,多个线程可能在同一个条件队列上等待不同的条件谓词,并且在最常见的加锁模式下公开条件队列对象。这些因素都使得无法满足notifyAll时所有等待线程为同一类型的需求。如果想编写一个带有多个条件谓词的并发对象,或者想获得出条件队列可见性之外的更多控制权,就可以使用显式的Lock和Condition而不是内置锁和条件队列,这是一种更灵活的选择。
以下代码给出了有界缓存的另一种实现,即使用两个Condition,分别为notFull和notEmpty,用于表示“非满”与“非空”两个条件谓词,代码如下:
package beidao.multithread.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionBoundedBuffer<T> {
private static final int BUFFER_SIZE = 100;
protected final Lock lock = new ReentrantLock();
//条件谓词:notFull(count < items.length)
private final Condition notFull = lock.newCondition();
//条件谓词:notEmpty(count > 0)
private final Condition notEmpty = lock.newCondition();
@SuppressWarnings("unchecked")
private final T[] items = (T[])new Object[BUFFER_SIZE];
private int tail, head, count;
//阻塞,直到:notFull
public void put(T x) throws InterruptedException{
lock.lock();
try{
while(count == items.length){
notFull.await();
}
items[tail] = x;
if(++tail == items.length){
tail = 0;
}
++count;
notEmpty.signal();
}finally{
lock.unlock();
}
}
//阻塞,直到:notEmpty
public T take() throws InterruptedException{
lock.lock();
try{
while(count == 0)
notEmpty.await();
T x = items[head];
items[head] = null;
if(++head == items.length)
head = 0;
--count;
notFull.signal();
return x;
}finally {
lock.unlock();
}
}
}
当使用显式的Lock和Condition时,也必须满足锁、条件谓词和条件变量之间的三元关系。在条件谓词中包含的变量必须由Lock来保护,并且在检查条件谓词以及调用await和signal时,必须持有Lock对象。
ReentrantLock的常用方法
- int getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
- int getQueueLength():返回正等待获取此锁定的线程估计数。
- int getWaitQueueLength(Condition):返回等待与此锁定相关的给定条件Condition的线程估计数。
- boolean hasQueuedThread(Thread):查询指定的线程是否正在等待获取此锁定。
- boolean hasQueuedThreads():查询是否有线程正在等待获取此锁定。
- boolean hasWaiters(Condition):查询是否有线程正在等待与此锁定有关的condition条件。
- boolean isFair():判断是不是公平锁,默认情况下ReentrantLock是非公平锁。
- boolean isHeldByCurrentThread():查询当前线程是否保持此锁定。
- boolean isLocked():查询此锁定是否由任意线程保持。
- void lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
- boolean tryLock():仅在调用时锁定未被另一个线程保持的情况下才获得该锁定。
- boolean tryLock(long timeout,TimeUnit unit):如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
# ReentrantReadWriteLock
类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。而JDK提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁ReentrantReadWriteLock来提升该方法的代码运行速度。
读写锁表示也有两个锁,一个是读操作相关的锁,也成共享锁;另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。