ReentrantLock、AQS 源码分析

如果 把ReentrantLock比做一个人的话,那么 AQS 就是他的灵魂。离开 AQS 谈论锁都是耍流氓

一.AQS使用方式和其中的设计模式

继承,模板方法设计模式

二.重要参数

  1. private volatile int state; 记录当前锁是否有线程拿到锁、一个线程进入锁的重入数。如果是0,代表没有任何线程进入锁。如果是 n,n>0 那么代表有个线程重入了 n 次
  2. AbstractOwnableSynchronizer: private transient Thread exclusiveOwnerThread:当前拿锁的线程
  3. volatile int waitStatus:竞争失败的线程会打包成Node放到同步队列,Node可能的状态里:
  • CANCELLED(cancelled 1):该节点的线程可能由于超时或被中断而处于被取消(作废)状态,一旦处于这个状态,节点状态将一直处于CANCELLED(作废),因此应该从队列中移除.
  • SIGNAL(signal -1):拿到锁的节点(head)和未拿到锁正常等待的节点 waitState 都是 signal 。当前节点为SIGNAL时,后继节点会被挂起,因此在当前节点释放锁或被取消之后必须被唤醒(unparking)其后继结点. 如:如果head 释放锁以后,就会判断 head.next 是否是 signal,是的话唤醒。
  • CONDITION(condition -2) :当前节点处于等待队列
  • PROPAGATE(propagate -3):共享,表示状态要往后面的节点传播
    0,表示初始状态(拿完锁的状态)
  1. private transient volatile Node head:等待队列的头
  2. private transient volatile Node tail:等待队列的尾

三.了解其中的方法

1.模板方法:

   独占式获取

   accquire
   acquireInterruptibly
   tryAcquireNanos

   共享式获取

   acquireShared
   acquireSharedInterruptibly
   tryAcquireSharedNanos

   独占式释放锁

   release

   共享式释放锁

   releaseShared

2.需要子类覆盖的流程方法

   独占式获取 tryAcquire
   独占式释放 tryRelease
   共享式获取 tryAcquireShared
   共享式释放 tryReleaseShared
   这个同步器是否处于独占模式 isHeldExclusively

3.同步状态state:

getState:获取当前的同步状态
setState:设置当前同步状态
compareAndSetState 使用CAS设置状态,保证状态设置的原子性

三、源码

1.lock

实现类源码:ReentrantLock 为例

final void lock() {
    
    
            acquire(1);
        }

AbstractQueuedSynchronizer 源码

//p1197  
 public final void acquire(int arg) {
    
    
 //tryAcquire 为需要覆盖,需要子类实现的方法
 //EXCLUSIVE表示正在一个独占模式下等待
        //如果一个线程执行 tryAcquire 恰好拿到了锁,那么就不再执行acquireQueued了。也不会放到待执行的队列了
        if (!tryAcquire(arg) &&
        //addWaiter 下面 p605
        //acquireQueued 下面857
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

//子类实现tryAcquire 方法示例
protected boolean tryAcquire(int arg) {
    
    
			if(compareAndSetState(0,1)) {
    
    
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}

如下为 addWaiter 方法 这一支的操作。

把调用 lock 的线程包装成 node 节点放到队列中,并把这个 node 节点返回
判断当前队列有没有尾节点(队列是否为空):

  1. 如果不为空那么就把当前节点挂在队列末尾(进行双链表的一些操作),设置当前节点为尾节点
  2. 如果为空,调用 enq 的方法,构建一个『空节点node ⇆ 当前线程 node』这样的双链表,enq 中使用自旋 CAS 来添加,防止有其他线程更改
 //p605
 //mode == null
private Node addWaiter(Node mode) {
    
    
// 使用当前线程 构造一个 node,构造器如下  p505
        Node node = new Node(Thread.currentThread(), mode); 
//如果当前有尾节点(等待队列不为空,非初始化)
        Node pred = tail;
        if (pred != null) {
    
    
//    当前节点的前驱节点地址赋值
            node.prev = pred;
//   使用 CAS 把当添加进来的节点设置成尾节点
            if (compareAndSetTail(pred, node)) {
    
    
                pred.next = node;
                return node;
            }
        }
// 如果当前没有尾节点(等待队列为空,初始化队列的时候) p583
        enq(node);
        return node;
    }
    
//p505    
    Node(Thread thread, Node mode) {
    
        
            this.nextWaiter = mode;
            this.thread = thread;
        }

如果尾节点为空(那么head也是空的,整个队列都是空的),才会执行enq。
所以此方法最后会形成一个『空节点node ⇆ 当前线程 node』,这样一个双链表

//p583
private Node enq(final Node node) {
    
    
//自旋
        for (;;) {
    
    
            Node t = tail;
            //队列初始化
            if (t == null) {
    
     
                //cas 方式设置 空节点 到 head 中,且 tail 与 head 同步
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
    
    
               //当前节点 的 上一个节点指针指向 if 中 造出来的那个  空节点
                node.prev = t;
                //cas 设置当前节点为尾节点
                if (compareAndSetTail(t, node)) {
    
    
                    //if 中 造出来的那个空节点的下一个节点指向当前节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

如下为acquireQueued 的操作

head 节点为已经拿到锁的线程
此方法基本思路
1.先拿到当前节点的上一个节点,如果上一个节点为head且当前节点尝试拿了一下锁拿到了。那么就把当前节点设置成head,上一个节点脱钩,返回 false
2.否则就会执行shouldParkAfterFailedAcquire、parkAndCheckInterrupt
3.感觉如果p == head && tryAcquire(arg)条件不满足循环将永远无法结束,当然不会出现死循环,奥秘在于parkAndCheckInterrupt会把当前线程挂起,从而阻塞住线程的调用栈(阻塞住的意思就是走到这个方法parkAndCheckInterrupt,就不动了,卡住了)。

//p857
final boolean acquireQueued(final Node node, int arg) {
    
    
        boolean failed = true;
        try {
    
    
            boolean interrupted = false;
            for (;;) {
    
    
                final Node p = node.predecessor();
                //如果他的前驱节点是头结点,且已经执行完毕(state 已经复位),执行tryAcquire后被我当前线程抢占了锁,那么执行把当前节点设置为head的操作
                if (p == head && tryAcquire(arg)) {
    
    
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
        //下面的两个方法主要是检查线程的状态,是否被中断。且阻塞当前线程(parkAndCheck) 
                //shouldParkAfterFailedAcquire p795
                //parkAndCheckInterrupt p835
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
    
    
            if (failed)
                //如果发生异常,会执行此段代码,取消加入队列
                cancelAcquire(node);
        }
    }

#p795

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            
          //This node has already set status asking a release to signal it, so it can safely park.
         //规则1:该方法首先检查前趋结点的waitStatus位,如果为SIGNAL,表示前趋结点会通知它,那么它可以放心大胆地挂起了,返回 true 后调用acquireQueued方法的 parkAndCheckInterrupt)将导致线程阻塞    
            return true;
        if (ws > 0) {
    
    
           //Predecessor was cancelled. Skip over predecessors and  indicate retry.
           //规则2:如果前趋结点是一个被取消的结点怎么办呢?那么就向前遍历跳过被取消的结点,直到找到一个没有被取消的结点为止,将找到的这个结点作为它的前趋结点,将找到的这个结点的waitStatus位设置为SIGNAL,返回false表示线程不应该被挂起。然后进去acquireQueued方法循环   
           //接下来acquireQueued循环会出现两种结果  
           //1.因为在这一步干掉了前一个被取消了的节点,把前前一个节点变成了前一个节点,恰巧前一个节点还是头结点,所以当前节点尝试tryAcquire可能会获取到锁。
           //2.前前一个节点不是头结点,那么就会再次进入到该方法。此次前一个节点肯定是SIGNAL状态,所以当前节点被毫无疑问的挂起(阻塞)
            
            do {
    
    
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
    
    
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
             //如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
p835
//这个方法的主要任务就是阻断线程
private final boolean parkAndCheckInterrupt() {
    
    
        LockSupport.park(this);
        return Thread.interrupted();
    }

总结

  • 子类实现 tryAcquire方法来具体操作拿锁的操作。
  • 用户调用acquire 方法,acquire 首先调用tryAcquire看看是否能拿到锁。如果拿到了那么返回 true,程序也能顺利往下执行,同时业务代码也会被执行
  • 如果拿不到,那么就把当前线程封装成一个 Node,通过调用addWaiter放到一个双链表当中。
  • addWaiter还会把这个node 返回,在acquireQueued方法会进行自旋,如果当前节点的前一个节点为头结点,本节点会再一次调用tryAcquire尝试进行拿锁的操作。
  • 如果拿不到接着执行,在shouldParkAfterFailedAcquire 中把前一个节点的 waitStatus 由默认的 0 改成 SIGNAL(-1),当前节点作为尾节点waitStatus状态还是 0。
  • 改完waitStatus状态之后,执行parkAndCheckInterrupt 阻塞当前线程。注:如果shouldParkAfterFailedAcquire返回false,&& 会短路,parkAndCheckInterrupt 阻塞当前线程。注:如果shouldParkAfterFailedAcquire就不执行了。但是acquireQueued是一个自旋,待执行shouldParkAfterFailedAcquire,waitStatus状态都改成-1后,还是会执行 parkAndCheckInterrupt来阻塞当前线程

2.unlock

public final boolean release(int arg) {
    
    
        //tryRelease aqs 的具体实现类重写
        if (tryRelease(arg)) {
    
    
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    
private void unparkSuccessor(Node node) {
    
    
    
    //设置 waitstatus 为0
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    
    Node s = node.next;
    //如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
    if (s == null || s.waitStatus > 0) {
    
    
        s = null;
        //为什么要从后往前找??原因在addWaiter方法:
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null){
    
    
        //释放 一个被阻塞的线程
        LockSupport.unpark(s.thread);
        }
}   

//我们从这里可以看到,节点入队并不是原子操作,也就是说,node.prev = pred; compareAndSetTail(pred, node) 这两个地方可以看作Tail入队的原子操作
//但是此时pred.next = node;还没执行,如果这个时候执行了unparkSuccessor方法,就没办法从前往后找了,所以需要从后往前找。
//还有一点原因,在产生CANCELLED状态节点的时候,先断开的是Next指针,Prev指针并未断开,因此也是必须要从后往前遍历才能够遍历完全部的Node。
private Node addWaiter(Node mode) {
    
    
	Node node = new Node(Thread.currentThread(), mode);
	Node pred = tail;
	if (pred != null) {
    
    
		node.prev = pred;
		if (compareAndSetTail(pred, node)) {
    
    
			pred.next = node;
			return node;
		}
	}
	enq(node);
	return node;
}

调用 LockSupport.unpark(s.thread); 释放当前线程阻塞,程序顺序往下执行业务代码。

四、ReentrantLock源码分析

1.构造器

参数为空或 false 是非公平锁。为 true 是公平锁。

    public ReentrantLock() {
    
    
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
    
    
        sync = fair ? new FairSync() : new NonfairSync();
    }

2. 非公平锁获取锁

static final class NonfairSync extends Sync {
    
    
    private static final long serialVersionUID = 7316153563782823691L;

    
    final void lock() {
    
    
        //如果直接修改了 state ,那么就证明现在没有其他资源在获得锁,本线程直接拿到锁。(原来锁就是操作了 一个 volitale 的 state)
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    //实现 aqs 的 tryAcquire
    protected final boolean tryAcquire(int acquires) {
    
    
        return nonfairTryAcquire(acquires);
    }
}


final boolean nonfairTryAcquire(int acquires) {
    
    
    final Thread current = Thread.currentThread();
    int c = getState();
    //如果是0,证明还没有线程拿到这个锁,直接 cas 设置 state
    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;
    }
    //如果当前锁已经被获取,且拿锁的线程不是当前线程,那么返回 false,tryAcquire 失败
    return false;
}

3.公平锁获取锁

    static final class FairSync extends Sync {
    
    
        private static final long serialVersionUID = -3000897897090466540L;

        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) {
    
    
                //hasQueuedPredecessors 是 公平锁的关键
                //对下面的 if 换一种好理解的写法
                /**
                    if(!hasQueuedPredecessors()){
                        if(compareAndSetState(0, acquires)){
                            setExclusiveOwnerThread(current);
                            return true;
                        }
                    }
                */
                //如果 hasQueuedPredecessors 返回 false,那么证明等待队列中没有线程在排队。 那么执行 compareAndSetState 拿锁
                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是公平锁加锁时判断等待队列中是否存在有效节点的方法。
//如果返回False,说明当前线程可以争取共享资源;如果返回True,说明队列中存在有效节点,当前线程必须加入到等待队列中。
//判断队列中是否有 有效节点,以真实线程节点 入队为准。不以虚节点(new Node())为准
public final boolean hasQueuedPredecessors() {
    
    

Node t = tail; 
Node h = head;
//head 的下一个元素
Node s;

//1.如果 h != t 为false (h == t 为 true) ,那么直接返回 false,在tryAcquire 中执行 set state 操作
//2.(s = h.next) == null && h != t  是走到了 enq ①处,此时 tail == node,head == new Node()。
//  head 的 next 还未指向 tail,但是 node 已入  tail。所以公平起见当前线程就不能再抢占锁了。   返回 true,使当前线程不会再设置 state  
//3.如果第二次拿锁的线程 和第一次拿线程的线程不是同一个线程,那么第二个线程应该让渡第一个线程(如果是同一个线程,那么两次拿锁就不分先后)
return h != t &&
    ((s = h.next) == null || s.thread != Thread.currentThread());
}

//分析此段代码需从 enq 方法看起
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;
            }
        }
    }
}

总结:

  1. NonfairSync 和 fairSync 的根本区别在于tryAcquire 中 当 state == 0 时(即当前锁没有被任何一个线程占有),当前线程是直接尝试拿锁(非公平)还是查看等待队列是否有元素来决定去排队还是尝试拿锁(当等待队列有元素就排队,没有元素就拿锁)。
  2. 公平锁直接的提现 方法是 hasQueuedPredecessors
  3. tryAcquire 有两个地方调用。1)lock 的时候直接调用一次。2)加入队列后再次尝试调用。
final void lock() {
    
    
            acquire(1);
        }

public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
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);
    }
}    

4.释放锁

protected final boolean tryRelease(int releases) {
    
    
    //可重入锁 state -1 
    int c = getState() - releases;
    //如果解锁线程 和 持锁线程不是同一个  抛异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    
    //如果当前线程的锁全部解完,那么就代表 此线程已经释放了锁
    if (c == 0) {
    
    
        //释放锁成功
        free = true;
        //持锁线程 释放
        setExclusiveOwnerThread(null);
    }
    //设置 state 数量
    setState(c);
    return free;
}

猜你喜欢

转载自blog.csdn.net/langwuzhe/article/details/108777510