ReentrantLock
我们主要看ReentrantLock的Lock,unlock这两个公平和非公平的实现 还有Condition的实现。
其实我一直都很疑惑,为啥ReentrantLock,CountDownLatch等类都是基于AQS实现的,那么为啥不直接继承AQS而是内部引用一个AQS的实现类呢,这里ReentrantLock给出了答案,它内部提供公平和非公平的实现,而这两个模式则是根据聚合不同的AQS来实现的,假如选择直接继承AQS的话,那么要提供者2种模式的实现就必须由两个类,ReentrantFairLock,ReentrantUnfairLock。不选择继承也可以给实现类更多的选择空间去继承自己需要的。
首先来看非公平模式的实现
lock
final void lock() {
//判断当前锁的状态 0未被获取 1已被获取
if (compareAndSetState(0, 1))
//没被占据那么就设置占有的线程为自己。
//(AQS的父类声明了一个局部变量,表示独占模式下拥有资源的线程)
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//当前状态
int c = getState();
//这里再次判断锁是否被获取
if (c == 0) {
//自旋是否能够获取锁
if (compareAndSetState(0, acquires)) {
//获取成功,设置当前线程为占有线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果锁被占有,并且占有线程时自己的话,
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
//int溢出直接抛异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//更新锁获取的次数,因为只有当前线程会更新锁的状态,所以不用担心多线程影响state
setState(nextc);
return true;
}
return false;
}
这里我们可以看到当锁已经占有时,nonfairTryAcquire中会判断占有线程是否是自己,如果是的话并不会阻塞当前线程,而是更新的state,也就是调用一次lock则state+=1。这里就是占有锁的线程多次调用Lock而不会被阻塞的原因。可以预测后面的unlock方法也是每调用一次state-=1,说明了如果需要解除锁的话,当前线程调用了几次Lock就得调用几次unlock。
unlock
public void unlock() {
sync.release(1);
}
release的代码上次独占模式的AQS中讲过,这里直接看tryRelease。
protected final boolean tryRelease(int releases) {
//和预测的一样获取锁-1之后的状态
int c = getState() - releases;
//判断是否是获取锁的线程调用,如果不是那就直接丢异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//状态为0那就是锁被当前线程释放了,清除表示占有锁的线程的引用即可。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//设置锁的状态 也就是减少一次锁的获取。
setState(c);
//如果是0那就返回true,表示需要唤醒同步队列中下一个等待的线程,不是0那就是说当前线程还占有锁。
return free;
}
可以看到非公平锁的代码非常简单。
公平锁实现
lock
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
可以看到这个方法和非公平的实现区别与一个方法hasQueuedPredecessors,我看他的实现
public final boolean hasQueuedPredecessors() {
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//头等于尾的话 就说明同步队列为空,或者当前节点的线程已经获取锁在执行了,
//又或者是当前线程就是头结点的线程,无论哪种情况,都说明当前线程之前并没有线程在休眠等待获取锁
//如果头不等于尾,先判断老二是否已经成功入队,如果没有入队说明那就返回true,不进行当前线程和老二之
//间的获取顺序控制,再判断老二是不是本线程的节点(这个判断很奇怪,已经入队了的老二怎么可能本方法的
//调用线程呢?老二只有在唤醒之后踢掉头结点之后才能返回acquire方法,这时候老二就不是老二了,我想到的
//解释就是其他调用者需要这个条件)
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
如果当前线程之前有排着队的线程,返回true。如果同步队列为空,或者当前线程是第一个节点,返回false。简单的来说是判断当前线程的优先级的。那就明确了,也就是说公平锁模式下,只是根据完全进入同步队列的时间(入队先后顺序)。非公平锁,入队之后的线程获取锁是按照先后顺序执行的,但是放锁之后,新的准备抢占锁的线程(未入队的线程) 可能比队列中的排队的第一个(释放锁之前的老二节点)早获取锁。
非公平锁的就是比公平锁更加快速把,在高并发的情况下,减少休眠,唤醒的操作,也就是减少了线程切换(后来的线程可能直接获取到锁,而不需要进行休眠,让出cpu时间),上下文切换。减少了很多cas操作,降低cpu消耗。
Condition类了
假如现在有一个情形,我有个钱包,里面有5000块钱,但是我这个钱包最多只能装10000块。设计一个并发的容器,当有多个线程给钱包拿钱加钱的情况。
下面是内部锁实现
public class Wallet_Sync {
private int money = 5000;
private final int max;
public Wallet_Sync(int max) {
super();
this.max = max;
}
public void addMoney(int param) throws InterruptedException {
if (money < 0) {
throw new IllegalArgumentException("input cannot be negative");
}
synchronized (this) {
System.out.println("begin addMoney " + param);
while (param + money > max) {
this.wait();
System.out.println(Thread.currentThread()+"wake");
}
money -= param;
this.notifyAll();
System.out.println("end addMoney " + param);
}
}
public void getMoney(int param) throws InterruptedException {
if (money < 0) {
throw new IllegalArgumentException("input cannot be negative");
}
synchronized (this){
while (money - param < 0) {
this.wait();
}
money -= param;
this.notifyAll();
}
}
}
public static void testWaitNotify() throws InterruptedException {
Wallet_Sync wallet_Sync = new Wallet_Sync(10000);
new Thread(() -> {
try {
wallet_Sync.addMoney(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(2 * 1000);
new Thread(() -> {
try {
wallet_Sync.addMoney(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread[Thread-0,5,main]begin addMoney 6000
Thread[Thread-1,5,main]begin addMoney 4000
Thread[Thread-1,5,main]end addMoney 4000
Thread[Thread-0,5,main]wake
结果可见,尽管不满足条件,第一个线程还是被唤醒了。内部锁是使用单一的同步队列实现阻塞的。当状态发生改变时,会唤醒所有等待状态变更的线程,被唤醒的线程等待的并不是该条件,或者是线程被唤醒之后,获取锁之前,该状态又被其他线程改变,从而又进行反复的休眠,唤醒操作,消耗性能。
public class Wallet_Condition{
private ReentrantLock lock = new ReentrantLock();
private int money = 5000;
private final int max;
private Condition notEnough = lock.newCondition();
private Condition alReadyMax = lock.newCondition();
public Wallet_Condition(int max) {
super();
this.max = max;
}
public void addMoney(int param) throws InterruptedException {
if (money < 0) {
throw new IllegalArgumentException("input cannot be negative");
}
lock.lock();
try {
System.out.println(Thread.currentThread() + "begin addMoney " + param);
while (param + money > max) {
alReadyMax.await();
System.out.println(Thread.currentThread() + "wake");
}
money += param;
notEnough.signal();
System.out.println(Thread.currentThread() + "end addMoney " + param);
} finally {
lock.unlock();
}
}
public void getMoney(int param) throws InterruptedException {
if (money < 0) {
throw new IllegalArgumentException("input cannot be negative");
}
lock.lock();
try {
while (money - param < 0) {
notEnough.await();
}
money -= param;
alReadyMax.signal();
} finally {
lock.unlock();
}
}
}
@Test
public static void testCondition() throws InterruptedException {
Wallet_Condition wallet_Condition = new Wallet_Condition(10000);
new Thread(() -> {
try {
wallet_Condition.addMoney(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(2 * 1000);
new Thread(() -> {
try {
wallet_Condition.addMoney(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread[Thread-0,5,main]begin addMoney 6000
Thread[Thread-1,5,main]begin addMoney 4000
Thread[Thread-1,5,main]end addMoney 4000
可见,第一个线程将不会被第二线程所唤醒。这里的显示锁实现可以见到模式和内部锁几乎一样,不一样的是显式锁可以设置多个同步队列,每个同步队列中的线程休眠,唤醒的条件可以不同。所以这里可以使用signal,因为是独占锁,只有一个线程能获取资源,而且时指定唤醒某个队列的线程,所以不用担心信号丢失的问题。所以不需要signalAll。signal与notify相比可以说是高效了非常多,notifyAll最坏的情况是O(N^2)时间复杂度,而signal则是O(1)。
下面可以看看源码实现。
public Condition newCondition() {
return sync.newCondition();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
public class ConditionObject implements Condition, java.io.Serializable{
//省略代码
public ConditionObject() { }
}
ConditionObject 其实是AQS中的一个非静态内部类,所以外部的AQS对象会持有一个ConditionObject 对象的引用。
先看await方法
/**
* Implements interruptible condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled or interrupted.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
//是否中断,中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//在Condition队列中加入当前节点
Node node = addConditionWaiter();
//释放所有当前线程获取的锁,因为await的触发肯定是对象的状态不满足执行条件,所以释放锁,从而使得
//其他线程能够获取锁 从而改变对象的状态,然后再唤醒对应的等待线程。
int savedState = fullyRelease(node);
//interruptMode 0-没有发生中断 THROW_IE(1)-中断的发生在signal之前,REINTERRUPT(-1)-中断的发生
//在signal之后 看方法注释 If interrupted while blocked in step 4, throw InterruptedException.
int interruptMode = 0;
//判断是否在同步队列,
while (!isOnSyncQueue(node)) {
//不在同步队列那么就发生阻塞
LockSupport.park(this);
//阻塞结束之后 判断是由中断还是signal引起的
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//这里的话node已经是进入了同步队列了 acquireQueued方法阻塞当前线程 直到node节点成为同步队列
//的头结点也就是获取了锁的状态savedState则是state增加的次数。返回值表示在这个过程中是否发生中断
//如果没发生中断,那么interruptMode 就不变。如果发生了中断并且在condition队列的自旋中没有发生中断
//或者中断在signal之后,那么就interruptMode =REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//去掉在condiiton中cancel掉的节点.
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//如果发生中断,根据interruptMode 做出具体的反应
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
/**
* Adds a new waiter to wait queue.
*
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//清除cancel的节点
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;
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取当前sync的状态如果是reentrantlock的话就是当前线程获取了多少次锁
int savedState = getState();
//全部释放,这是释放一定要成功的,失败就抛异常
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
final boolean isOnSyncQueue(Node node) {
//如果节点状态是CONDITION的话,直接返回false,因为当节点被signal时会修改CONDITION为0,然后enq
//加入同步队列。这里返回false使得线程park
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果有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.
*/
//前两个判断都没返回的话,就在同步队列中一个一个找,如果找到本身的话就返回true
return findNodeFromTail(node);
}
//一个一个在同步队列中找,在到就返回true
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
private int checkInterruptWhileWaiting(Node node) {
//返回中断情况,没发生中断返回0,发生了中断就transferAfterCancelledWait方法判断
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
//此方法用于判断中断发生在signal之前还是之后,signal之前 则checkInterruptWhileWaiting返回throw_ie通知上层
//抛出异常,REINTERRUPT那么就通知上层重新设置标志位 因为Thread.interrupted()会重置中断状态
final boolean transferAfterCancelledWait(Node node) {
//设置node状态是否成功,在signal之后,调用signal的线程会设置node的状态为0,所以如果这里判断成功的话
//那么中断发生于signal之前,因为状态没有发生改变嘛!
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//进入同步队列,然后返回true,通知上层抛ie。
enq(node);
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
//这里就是signal发生于中断之前,signal线程会调用enq,把node置入同步队列。这里方法退出的时候,马上会
//调用acquireQueued,所以此方法退出前要确保node已经在同步队列了。所以这里如果不在同步队列的话就让出
//cup时间,等enq完成。
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
//可以看到,根据mode来判断具体是抛中断异常还是重置中断状态
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
signal
public final void signal() {
//先判断是不是独占模式,不是直接丢异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
//按顺序的找到不是cancel的节点,调用transferForSignal
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//上面也说了,线程调用signal时会修改node的状态为0,失败的话说明node已经cancel
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//调用enq 进入同步队列
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
condition的代码就到这里了.