文章目录
1.ReentranLock源码分析
因为ReentranLock里面的sync是基于AQS(AbstractQueuedSynchronizer
),并且AQS采用了模板方法的设计模式,因此,诸如semephore
、countdownlatch
、cylicbarrier
都是基于AQS,思想上也是大同小异,因此拿ReentranLock
来举例说明。
AQS底层是基于一个volatile修饰的state变量,以及先进先出的双向队列来构成,根据每个工具类的不一样,state变量的含义也不一样,像ReentranLock代表是线程的可重入次数,比如state如果为0,代表此时线程可以去尝试获取锁。
构造函数
如果是用默认构造函数,默认是创造非公平锁。
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
public ReentrantLock() {
sync = new NonfairSync();
}
lock
调用lock.lock()
之后会进入到NonfairSync里的lock方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
非公平锁就体现在这里,无论什么,他都会尝试先获取锁。调用lock方法之后,会直接尝试去修改state变量从0修改为1,如果修改成功,则将当前独占线程设置为当前线程。
如果不成功则会进入acquire方法。
AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
需要注意的是acquire方法是在AQS.java文件里的,这里就体现了模板方法的思想,因为tryAcquire
方法是由实现类sync去实现的,而其他的方法如addWaiter
、acquireQueue
方法都是属于AQS里面的。但是定义了一个顺序。
首先会去调用tryAcquire方法。
ReentranLock.java
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;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这里会再尝试去获取锁,如果当前的state的值为0,则会尝试去修改state变量的值,从0修改为1,如果修改成功,同样也将锁的独占线程设置为当前线程。 如果当前锁的独占线程就是当前线程,那么只要在原来的state变量进行修改就可以了。
如果获取锁失败则会进入到addWaiter中。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
首先会拿到当前队列的尾结点,因为此时尾结点为空,那么就会创造一个虚拟头结点,这个虚拟头结点不代表任何线程,然后会生成一个代表当前线程的结点添加进队列的尾部中。 并返回当前结点。
后面就执行acquireQueued方法。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
首先会根据传入的结点做判断,首先会有一个死循环,里面做了一些判断,首先会获取当前结点的前驱,如果前驱是头结点的话,那么会再尝试去获取锁,如果获取锁成功则将当前结点设置为头结点直接返回。 如果获取锁失败或者当前结点不是头结点,则会将当前结点的头结点设置为Singal状态,然后再调用Locksupport.park方法将该线程阻塞。
需要注意的是当前结点的前驱结点不一定是目标结点,因为有可能前驱结点的waitStatus是>0的,>0代表也就是waitstatus等于2,2代表是cancel的意思,为什么会出现cancel结点,我这里是以第一次加锁的情景来演示整个过程,其实如果在使用lock一段时间了,因为ReentranLock可以做到在获取锁的时候可以响应中断,也就是原本已经放进队列里的结点响应了中断要从队列里出队,为了表示出队这个操作,因此就把waitstatus设置成2,代表是取消的意思。
因此shouldParkAfterFailedAcquire
是将当前结点的前驱结点设置成waitstatus为-1,但是会经历一个从后往前迭代的过程,因为中间有很多是取消的结点了,目的是为了找出真正的那个前驱结点。
Singal状态
代表当前结点的后继结点可以被唤醒。
unlock
调用lock.unlock之后,会进入
AQS.java
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
这里会调用tryRelease
方法将对应的state变量减少
ReentranLock.java
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
因为state变量代表的是可重入次数,同一线程重入多少次,那么就应该解锁多少次。
然后获取队列头结点,唤醒后继结点unparkSuccessor
。需要注意的是unparkSuccessor
跟之前的shouldParkAfterAcquireFail
一样,说是头结点的后继结点,但是实际上严谨一点是找离头结点最近的waitstatus<=0的结点。拿到队列的尾结点,后尾开始遍历,找离头结点最近的第一个waitstatus<=0的结点。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
然后就回到了accquiredQueue里面的那个for的死循环里面,被唤醒的结点会重新再次尝试获取锁,如果获取锁成功则将锁的独占线程设置为当前结点,并且reture。
方法调用链
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
公平锁就体现在lock的时候,不会首先尝试去修改,直接进行acquire操作。
解锁过程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
2.AQS常见套路
其实可以发现,在ReentranLock里面很多方法都是来自AQS这个抽象类,对于AQS来说,只要自定义state的含义,自己实现对应的tryAcquire
、tryAcquireShared
、tryRelease
、tryAcquireRelease
就可以了,其他方法都已经在AQS有实现。
比如常见的CountdownLatch
和Semaphore
,其中每个实现类都实现了自定义的tryAcquireShared
的方法,其他都一样。
//AQS.java -- latch.await()
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//CountdownLatch.java -- latch.await()
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//AQS.java -- semephore.acquired()
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//Semephore.java -- semephore.acquired()
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//相同的方法获取state
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
再来看一下释放方法
//AQS.java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//CountdownLatch.java -- latch.countdown()
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
//AQS.java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
//Semephore.java -- semephore.release()
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
//公用的释放方法
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
可以发现无论是哪个实现类,如果是获取锁都会走到AQS.acquireSharedInterruptibly
,在里面调用自定义的tryAcquireShared()
还有AQS.doAcquireShared()
。
或者AQS.releaseShared
,在里面调用自定义tryReleaseShared()
还有AQS.doReleaseShared()
。
而在AQS里面的获取锁、释放锁资源都是之前ReentranLock里面的相同配方。
3.自定义一次性闸门
所有线程申请锁的时候都会阻塞,等到调用sinal将state设置成1。
public class OneShotLatch {
public static Sync sync = new Sync();
public void await() {
sync.acquireShared(0);
}
public void sinal() {
sync.releaseShared(0);
}
private static final class Sync extends AbstractQueuedSynchronizer {
@Override
protected int tryAcquireShared(int arg) {
return getState() == 1 ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(int arg) {
setState(1);
return true;
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(5);
OneShotLatch latch = new OneShotLatch();
for (int i = 0; i < 5; i++) {
int finalI = i;
service.submit(() ->{
System.out.println("线程"+ finalI+"正在尝试获取");
latch.await();
System.out.println("线程"+ finalI+"获取成功");
});
}
Thread.sleep(2000);
latch.sinal();
}
}
4.ReentranLock跟Synchronized的相同点和不同点
相同点:
- 两个都是可重入锁,都属于独占锁。
不同点:
-
ReentranLock属于JDK层面,底层是基于JUC包的AQS。而Synchronized是基于JVM的。最好的体现就在已经获得锁的了在执行的过程中,出现了异常,ReentranLock需要在finally中手动unlock(),而Synchronized会在JVM底层自动解锁。
-
ReentranLock可以实现公平锁,而Synchronized只能是非公平锁。
-
对于一个获取锁的线程而言,要么能够获取锁,要么就不能获取锁而等待,这是属于Synchronized层面的。但如果是ReentranLock的话有多种获取锁的形式,比如
lock()
就是普通的获取锁,而tryLock()
支持传入时间,在一定时间内获取不了锁,就不会再去获取锁,而lockInteruppty()
支持等待锁或者获得锁的时候响应中断。 -
ReentranLock可以配合Condition对象实现更加灵活的线程通知和等待,比如await、singal等等。
5.ReentrantReadWriteLock 读写锁
因为ReentrantLock
是独占锁,如果是读取情况某些内容的时候也会阻塞,效率不高,因此Lock还有一个具体实现类叫做ReentrantReadWriteLock
,做到读读不阻塞,读写阻塞,底层也是基于AQS。
需要注意的是里面也同样只有一个state变量,怎么表示读锁或者写锁重入次数呢?
其实将state变量进行拆分,高16位表示读锁数量,而低16为表示写锁数量。
写锁加锁
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState(); //获取共享变量state
int w = exclusiveCount(c); //获取写锁数量
if (c != 0) { //有读锁或者写锁
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread()) //写锁为0(证明有读锁),或者持有写锁的线程不为当前线程
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires); //当前线程持有写锁,为重入锁,+acquires即可
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires)) //CAS操作失败,多线程情况下被抢占,获取锁失败。CAS成功则获取锁成功
return false;
setExclusiveOwnerThread(current);
return true;
}
简单来说就是获取当前state变量的值,并且获取当前的独占锁也就是写锁的数量,如果当前state变量的值不为0,那么肯定存在读锁或者写锁,然后再去判断当前的独占锁数量是不是为0,如果是为0,说明有读锁,获取写锁失败,添加进队列中。
如果不为0,说明是写锁,那么会去判断当前exclusiveThread
是不是currentThread
,如果是的话那么就会通过CAS修改state变量,代表可重入次数。如果当前exclusiveThread
不是currentThread
,那么也会添加进队列进行等待。
读锁加锁
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current) //写锁不等于0的情况下,验证是否是当前写锁尝试获取读锁
return -1;
int r = sharedCount(c); //获取读锁数量
if (!readerShouldBlock() && //读锁不需要阻塞
r < MAX_COUNT && //读锁小于最大读锁数量
compareAndSetState(c, c + SHARED_UNIT)) { //CAS操作尝试设置获取读锁 也就是高位加1
if (r == 0) { //当前线程第一个并且第一次获取读锁,
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) { //当前线程是第一次获取读锁的线程
firstReaderHoldCount++;
} else { // 当前线程不是第一个获取读锁的线程,放入线程本地变量
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
同样也是拿到state变量的值,判断当前的独占锁数量和独占线程存不存在,如果不存在,说明当前是读锁,还不能共享,会再去判断当前队列的头结点是不是属于写线程,如果是写线程也会加入到队列中,也是为了避免写线程过度饥饿,如果不是写线程,会通过CAS来修改state变量的值。