可重入锁是指当某个线程已经持有了这把锁,但是某个时刻,这个线程还要尝试再次拿到这把锁,支持这种可重入的实现就是可重入锁;
以ReentrantLock可重入锁来看,其state表示重入次数,当想要再次拿到这把锁的时候,state+1;当想要释放这把锁的时候
state-1,因此可以根据state是否等于0来判断这把锁是否被某个线程锁持有
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}}
使用上比较简单,推荐的做法是在finally块中释放锁,因为这样在出现异常的时候可以及时释放锁资源
关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,
ReentrantLock也同样可以借助于Condition实现等待/通知模式
在了解ReentrantLock实现AQS之前,先来看看AQS的实现类一般如何去操作…
一般要通过AQS实现自己的同步器类有以下三步:
需要注意的的是,state在ReentrantLock的含义表示的是重入次数
ReentrantLock是一种独占模式,相应的会实现tryAcquire和tryRelease方法
在Condition中有两个名词需要做区分
另外ReentrantLock大部分实现都是由AQS完成,在上篇博文中已经对AQS做了详细分析,因此这里不在过多重复分析…
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
// 非公平锁,tryAcquire方法由子类实现
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 拿到当前锁对象的重入次数
int c = getState();
// 如果等于0说明该锁对象没有被任何对象持有
// 这个时候等待队列可能是有等待节点的,只是恰好锁资源在此刻被释放了
if (c == 0) {
// 这里尝试去抢这把锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果此锁被当前对象持有,也就是重入操作,累加state
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;
}
// 尝试释放锁资源
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);
return free;
}
protected final boolean isHeldExclusively() {
// 判是否独占模式
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 创建Condition对象
final ConditionObject newCondition() {
return new ConditionObject();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 重入次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// state=0表示此锁未被占用
final boolean isLocked() {
return getState() != 0;
}
}
//
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
// 尝试获取锁
// 这个时候等待队列可能是还有等待节点的,这里取尝试抢一下;
// 如果锁资源这个时候刚好被释放了,这里是有可能抢成功的
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 既然没有抢成功,那就老老实实取获取锁资源
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
//
公平锁与非公平锁的最大区别在于,非公平锁会尽可能的去抢占资源(尽管等待队列存在很多等待节点),
而公平锁,如果等待队列里存在等待节点,那它是不会去抢占资源的,放进队列,然后按先进先出的顺序去获取资源
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 获取锁,如果拿不到会进入阻塞队列中等待
final void lock() {
acquire(1);
}
// 尝试拿取锁,成功则返回true,失败返回false
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 拿到重入次数
int c = getState();
// 说明这把锁未被任何线程持有,可以尝试获取锁
if (c == 0) {
// 和非公平锁的唯一区别是,这里多了hasQueuedPredecessors判断条件
// 意思是:首先判断在等待队列里面没有任何等待节点,它才会尝试取获取资源,
// 否则的话,就不去争抢锁资源了,毕竟是先来先服务嘛(保证公平性)
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { // CAS设置状态值
// 说明获取锁资源成功了,在锁对象中设置exclusiveOwnerThread=当前线程,表明此锁被当前线程锁住了
setExclusiveOwnerThread(current);
return true;
}
}
// 判断是否当前线程持有,是的话,就是重入持有,改变state的值
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
//
在特定条件上等待锁资源
来看个例子:
/**
* 使用多个Condition实现通知部分线程
*/
public class ReentrantLockExample {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
public void awaitA() {
lock.lock();
try {
System.out.println("start awaitA at " + System.currentTimeMillis()
+ " ThreadName:" + Thread.currentThread().getName());
conditionA.await();
System.out.println("end awaitA at " + System.currentTimeMillis()
+ " ThreadName:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB() {
lock.lock();
try {
System.out.println("start awaitB at " + System.currentTimeMillis()
+ " ThreadName:" + Thread.currentThread().getName());
conditionB.await();
System.out.println("end awaitB at " + System.currentTimeMillis()
+ " ThreadName:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signalAll_A() {
lock.lock();
try {
System.out.println("signalAll_A at " + System.currentTimeMillis()
+ " ThreadName:" + Thread.currentThread().getName());
conditionA.signalAll();
} finally {
lock.unlock();
}
}
public void signalAll_B() {
lock.lock();
try {
System.out.println("signalAll_B at " + System.currentTimeMillis()
+ " ThreadName:" + Thread.currentThread().getName());
conditionB.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Thread a = new Thread(() -> {
example.awaitA();
});
a.setName("A");
a.start();
Thread b = new Thread(() -> {
example.awaitB();
});
b.setName("B");
b.start();
example.signalAll_A();
/**
* print: 从输出结果可以看出只有线程A被唤醒了
*
* start awaitA at 1596380331589 ThreadName:A
* start awaitB at 1596380331590 ThreadName:B
* signalAll_A at 1596380331590 ThreadName:main
* end awaitA at 1596380331590 ThreadName:A
*/
}
}
//
在以上例子中,想要实现的效果是 使用多个Condition实现通知部分线程,也就是将唤醒粒度变小
来看看Condition的实现原理
创建Condition对象,这里的ConditionObject是AQS的内部类
final ConditionObject newCondition() {
return new ConditionObject();
}
AbstractQueuedSynchronizer#ConditionObject.await()
也就是我们例子中通过conditionA.await()进入阻塞状态,等待其他线程调用signalAll或者signal唤醒
public final void await() throws InterruptedException {
// 如果当前线程已经被中断了,就响应中断(也就是抛出异常)
if (Thread.interrupted())
throw new InterruptedException();
// 将当前线程包装成Node放入条件等待队列的队尾
Node node = addConditionWaiter();
// 同时还有释放当前线程已获取的资源
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果当前节点不在同步队列里,那就将线程挂起
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 说明当前节点已经从条件队列移到了同步队列中(也就是从await状态被signal唤醒之后,可以尝试获取锁资源进行后续操作了)
// 从上面被挂起的地方被唤醒之后,尝试去获取锁资源,如果获取失败,那就会进入等待队列中
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 记录中断信息
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//
主要操作
AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter
将当前线程包装成Node放入条件等待队列的队尾
/**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
// 从条件等待队列中移除取消的节点
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//
AbstractQueuedSynchronizer.ConditionObject#unlinkCancelledWaiters
从条件等待队列中移除被取消的节点
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
//
AbstractQueuedSynchronizer#fullyRelease
final int fullyRelease(Node node) {
boolean failed = true;
try {
// 拿到当前线程锁占用的资源,然后释放
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
//
AbstractQueuedSynchronizer#isOnSyncQueue
判断当前节点是否在同步器队列中,如果是的话说明当前节点正在等待获取资源
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 注意:在条件等待队列用的nextWaiter来表示下一个节点
// 因此如果next不为空说明已经在同步队列里面了
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
// 从队尾遍历找到这个节点
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
//
AbstractQueuedSynchronizer.ConditionObject#signal
public final void signal() {
// 必须满足是独占的模式下
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
// 进行唤醒
doSignal(first);
}
AbstractQueuedSynchronizer.ConditionObject#doSignal
// 尝试唤醒条件等待队列中的一个节点,直到唤醒一个为止
// 遇到取消节点就跳过,如果到队列末端都没有成功,那就结束,说明这个队列没有可唤醒的节点
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
// AbstractQueuedSynchronizer#transferForSignal
final boolean transferForSignal(Node node) {
// 在条件等待队列中有两种状态,要么是CONDITION,要么是CANCELED
// 如果不是CONDITION,说明是被取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 从条件等待队列中进入到同步队列
Node p = enq(node);
// enq返回的是当前接的前驱节点,如果前驱节点被取消了或者我们设置前驱节点为SIGNAL状态失败了
// 那我们就自己唤醒当前节点(将前驱节点设置为SIGNAL,最后也是LockSupport.unpark进行唤醒,只不过是在同步队列中等待前驱节点的唤醒而已)
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
//
signalAll和signal原理类似,只不过一个是唤醒所有在当前condition上等待的节点,另一个是只唤醒一个,这里不在赘述.
以上是个人理解,如果问题请指出,谢谢!