Java 多线程--ReentrantLock & AQS源码分析

1.ReentranLock源码分析

因为ReentranLock里面的sync是基于AQS(AbstractQueuedSynchronizer),并且AQS采用了模板方法的设计模式,因此,诸如semephorecountdownlatchcylicbarrier都是基于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去实现的,而其他的方法如addWaiteracquireQueue方法都是属于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的含义,自己实现对应的tryAcquiretryAcquireSharedtryReleasetryAcquireRelease就可以了,其他方法都已经在AQS有实现。

比如常见的CountdownLatchSemaphore,其中每个实现类都实现了自定义的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变量的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值