ReentrantLock
,谓之重入锁,可完全替代synchronize
关键字。虽然JDK6开始对synchronize
做了大量的优化(比如采用自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销)使得两者性能差距不大,但ReentrantLock
还是比之更灵活更强大,对开发人员也更友好。建议除非对简单的代码块加锁,其他情况最好还是用ReentrantLock
。
1、常规操作
ReentrantLock
的两个构造函数源码:
//两个构造函数,默认为非公平锁,带参构造函数可以传入bool值,true为公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
我们可以这样调用:
boolean fair = false;
ReentrantLock lock = new ReentrantLock(fair);
lock.lock();//获取锁
try{
...
}finally{
lock.unlock();//最好在finally块释放锁
}
注:一般情况下,建议使用默认无参构造函数,也就是非公平锁。因为公平锁会由AQS(AbstractQueuedSynchronizer
,JUC并发包的核心基础组件)维护一个CLH同步队列,遵循FIFO原则,一定程度上损耗了性能。
2、 限时等待
ReentrantLock
为了避免死锁,还提供了tryLock
方法,我们可以限时等待资源的申请。示例如下:
public class LockTest implements Runnable{
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try{
if (lock.tryLock(5, TimeUnit.SECONDS)){
Thread.sleep(5100);
}else {
System.out.println(Thread.currentThread().getName() + " 获取锁失败");
}
} catch (InterruptedException e) {
//e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + "是否持有锁: "
+lock.isHeldByCurrentThread());
if(lock.isHeldByCurrentThread()) lock.unlock();
}
}
public static void main(String[] args){
LockTest lockTest = new LockTest();
Thread thread_1 = new Thread(lockTest,"thread_1");
Thread thread_2 = new Thread(lockTest,"thread_2");
thread_1.start();
thread_2.start();
}
}
结果:
thread_2 获取锁失败
thread_2是否持有锁: false
thread_1是否持有锁: true
可以看到,thread_1首先持有锁5100ms,thread_2进行限时5000ms的tryLock
,结果获取锁失败,不再继续等待,执行else
代码块的内容,这样就可以避免一直等待带来的CPU浪费和可能的死锁。当然,如果thread_1获取锁后执行时间少于5000ms,unlock
后thread_2就可以立马获取资源,进行操作了,大家可以自行尝试。
3、 中断响应
ReentrantLock
还支持外部中断线程,只需要在获取锁的时候改调lock.lockInterruptibly()
方法,这样thread_1就有了响应中断的能力,调用thread_1.interrupt()
后,thread_1就会放弃对锁的占有。
无论是哪种获取方式,各有其用途,核心方法为TryAcquire
,源码及注释如下:
//非公平锁的获取
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前锁同步状态
int c = getState();
//state == 0,表示该锁处于空闲状态
if (c == 0) {
//获取锁成功,设置为当前线程所有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//线程重入
//判断持有锁的线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//公平锁的获取
final boolean tryAcquire(int acquires) {
...
}
我们再看下继承关系:
//都是ReentrantLock的内部类。Sync 继承了AQS,并通过多态实现了公平锁和非公平锁的获取和释放等功能。
abstract static class Sync extends AbstractQueuedSynchronizer {...}
static final class NonfairSync extends Sync {...}
static final class FairSync extends Sync{...}
4、 Condition
Condition
应用比较简单,如下:
public class LockTest implements Runnable{
private static ReentrantLock lock = new ReentrantLock();
//获取lock的condition
private static Condition condition = lock.newCondition();
@Override
public void run() {
try{
lock.lock();
System.out.println("条件等待~~~");
condition.await();
System.out.println("获取信号,继续执行~~~");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException{
LockTest lockTest = new LockTest();
Thread thread_1 = new Thread(lockTest,"thread_1");
thread_1.start();
Thread.sleep(2000);
lock.lock();//thread_1释放锁后,主线程拿到锁
System.out.println("主线程获取锁");
condition.signal();//通知thread_1,可以执行了
System.out.println("主线程准备释放锁...");
Thread.sleep(2000);
lock.unlock();//主线程释放锁
System.out.println("主线程已经释放锁");
}
}
结果:
条件等待~~~
主线程获取锁
主线程准备释放锁... //这里会等待2000ms
主线程已经释放锁
获取信号,继续执行~~~
从结果可以看出,condition.await()
唤醒的条件除了signal()
,还必须重新获取锁才可以。看源码也可以看出来,这里就不贴了。
除了await()
和signal()
,condition还提供了awaitUninterruptibliy()
、awaitUntil(Date deadline)
等方法。