一、可重入锁ReentrantLock
可重入锁可以完全替代synchronized,与synchronized相比,ReentrantLock是显式操作,需要手动指定何时加锁,何时释放锁。
之所以称之为可重入锁,是因为一个线程可以连续两次获得同一把锁,例如:
lock.lock();
lock.lock();
try{
i++;
}
finally{
lock.unlock();
lock.unlock();
}
如果不允许这种操作的话,那么将会在第二次请求锁的时候和自己产生死锁。注意,一个线程多次获得锁,释放锁的时候,必须释放相同数目的锁。
•中断响应
对于synchronized来说,如果一个线程在等待锁,那么只有两种结果,一个是获得锁后继续执行,一个是保持等待。对于可重入锁来说还有额外的一种情况,那就是线程可以被中断。这有助于解决死锁问题。加锁的时候使用lock.lockInterruptibly(),这样线程就可以响应中断。
•锁申请等待限时
ReentrantLock中有一个tryLock()方法,在申请锁的时候会等待一段时间,超时后如果还得不到锁,则线程会自动放弃,这也可以有效解决死锁问题。tryLock()方法有两个参数,第一个表示等待时长,第二个表示计时单位。比如,tryLock(5, TimeUnit.SECONDS)表示等待5秒,如果5秒之内获得锁,则返回true;如果超过5秒仍未获得锁,则返回false。tryLock()也可以不带参数,那么它就会立即返回true或者false。
•公平锁
大多数情况下,锁的申请都是非公平的。也就是说现申请锁的线程不一定先得到锁。系统只是会从锁的等待队列中随机选择一个线程。而公平锁就是按照时间先后顺序,先申请者先得,后申请者后得。synchronized就是非公平锁,使用ReentrantLock可以设置锁的公平性。其有一个构造函数:public ReentrantLock(boolean fair)。当fair为true的时候就是公平锁,默认是非公平锁。
实现公平锁必然要求系统维护一个有序队列,实现成本较高,性能较低。一般情况下,不需要使用公平锁。
ReentrantLock的方法总结如下:
lock():获得锁,如果锁已经被占用则等待。
lockInterruptibly():获得锁,但是优先响应中断。
tryLock():尝试获得锁,如果成功,返回true,失败返回false。该方法不等待。
tryLock(long time, TimeUnit.unit):在给定的时间内尝试获得锁。
unlock():释放锁。
二、Condition条件
Condition是和重入锁关联使用,通过Lock接口的Condition newCondition()方法可以生成一个与当前重入锁绑定的Condition实例。Condition接口提供的方法如下:
await()方法会使当前线程等待,同时释放当前锁,当其他线程调用signal()/signalAll()方法时,线程可以获得锁并继续执行。调用该方法的线程可以响应中断,调处等待。
awaitUninterruptibly()方法,该方法与await()方法类似,只不过是不会在等待过程中响应中断。
singal()方法用于唤醒一个在等待中的线程。而signalAll()会唤醒所有在等待的线程。
在调用await()和signal()/signalAll()之前,线程需要先获得相应的重入锁,而且在调用await()时还需要捕获异常。同样的,调用await()方法之后,线程也会释放锁,而且在该线程被唤醒后也需要再次获得相关的重入锁才能继续执行。
如下示例:
public class ReentrantLockCondition implements Runnable{
private static ReentrantLock lock=new ReentrantLock();
private static Condition condition=lock.newCondition();
public static void main(String[] args) throws InterruptedException {
ReentrantLockCondition rlc=new ReentrantLockCondition();
Thread t1=new Thread(rlc);
t1.start();
Thread.sleep(100);//主线程等待一会,否则主线程先获得锁就会导致signal()先执行,那t1就不会被唤醒了
lock.lock();
condition.signal();//先获得锁
lock.unlock();
}
public void run() {
try{
lock.lock();
condition.await();//先加锁,还要捕获异常
System.out.println("Thread is going on");
}
catch(InterruptedException e){
e.printStackTrace();
}
finally{
lock.unlock();
}
}
}