public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock(true);
CountDownLatch countDown = new CountDownLatch(2);
new Thread(() -> {
lock.lock();
lock.lock();
lock.lock();
log.info("第一次解锁");
lock.unlock();
log.info("第二次解锁");
lock.unlock();
log.info("第三次解锁");
countDown.countDown();
}).start();
new Thread(() -> {
log.info("线程2尝试获取锁");
lock.lock();
log.info("线程2获取到锁");
countDown.countDown();
}).start();
countDown.await();
log.info("程序正常运行完毕");
}
这里为了方便演示锁的释放没有写在finally代码块里面,但是实际使用的时候unlock()操作必须写在finally代码块里面,在这段代码中开启了两个线程进行锁资源的抢夺,上面那个线程对同一个锁加锁了三次,只释放了两次,那么锁资源得不到释放从而造成下面的线程一直获取不到锁资原被挂起,整体效果通过CountDownLacth来展现,
分析结果得出,可重入性是指同一个线程多次获取锁资源,对应的在编写代码时我们需要注意锁的释放必须与获取锁资源次数相等。
ReentrantLock被称为重入锁,内部实现通过AQS完成的,并且维护了一个公平锁和非公平锁。本文首先讲解Sync及其内部实现,以及FairSync和UnFairSync怎样实现公平与非公平的;接着对锁的获取与释放,以及条件Condition的使用进行分析。
那么让我们先来看看Sync内部实现吧,
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取资源数
int c = getState();
// 如果c为0,尝试原子的设置状态
// 设置的值可以由我们指定
// 设置成功则将当前线程设置为锁资源占有者
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果当前线程为之前设置的锁资源占有者
// 执行if语句块里面的
// 这里是被称为重入的关键点
// 如果尝试获取锁资源的线程之前获取到锁资源
// 那么该线程可以再次获取锁资源
else if (current == getExclusiveOwnerThread()) {
//
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 这里不需要原子的设置
// 因为判断的当前线程为之前锁资源的持有者
setState(nextc);
// 设置成功返回true
return true;
}
// 整个流程执行完,尝试获取锁都失败
return false;
}
这种实现之所以被称为非公平锁,是因为当进行尝试获取锁时,并未对排在同步队列之前的线程进行判断而直接进行锁资源的抢占,接下来看看tryRelease()方法,
protected final boolean tryRelease(int releases) {
// 将当前锁资源减去释放值
int c = getState() - releases;
// 如果当前当前线程不是锁资源持有线程
// 抛出非法监视着异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否成功释放标识
boolean free = false;
// 如果锁资源降为0,则代表当前锁已经被释放完毕
if (c == 0) {
free = true;
// 锁资源未被任何线程持有
setExclusiveOwnerThread(null);
}
// 重新设置锁资源状态
setState(c);
// 有两种结果
// 1.返回true,当前线程占用锁资源完毕释放锁
// 2.false,锁资源还在被当前线程持有
return free;
}
从上面的分析可以看出,Sync默认实现为非公平的(synchronized关键字保持的获取方式),下面来看公平锁与非公平锁,公平锁与非公平锁都是通过继承Sync来实现。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 原子的判断当前锁资源是否为0
// 如果是0则设置当前资源为1
if (compareAndSetState(0, 1))
// 成功执行将当前线程设置为锁资原持有线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 失败则调用acquire方法尝试获取锁资源
acquire(1);
}
// 直接调用Sync的nonfairTryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 直接acquire尝试获取锁
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 相比于非公平锁,公平锁会判断同步队列中是否有已经等待的节点
// 如果有已经等待的节点,则获取锁资源失败
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
ReentrantLock重入锁,获取锁即是获取到资源的过程,释放锁的过程与之相反,
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
lock()操作调用了acquire()方法,unlock()操作调用的release(),调用过程见我之间分析的AQS——同步队列独享模式。
条件Condition的使用通过AQS的条件队列实现,由于某些条件不满足可以调用Condition.await()方法从而让当前线程挂起并加入到条件队列,当其他线程执行后,满足条件了则唤醒当前线程重新执行接下来的程序流程,下面演示一个例子:
@Slf4j
public class ReentrantLockExample2 {
/**
* volatile 修饰boolean做状态标识量
* 保证每次读取都是最新值
*/
private static volatile boolean flag = true;
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
try {
while (flag) {
condition.await();
}
log.info("成功达成条件");
} catch (InterruptedException e) {
log.info(e.getMessage());
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
lock.lock();
try {
flag = false;
log.info("唤醒条件");
condition.signal();
}finally {
lock.unlock();
}
}).start();
}
}