ReentantLock源码分析,加解锁,以及AQS队列解析

ReentantLock源码学习

1.ReentantLock类结构解析

public class ReentrantLock implements Lock,

ReentrantLock实现了锁接口,内部有一个抽象类Sync继承了AbstractQueuedSynchronizer(AQS)。

abstract static class Sync extends AbstractQueuedSynchronizer

ReentrantLock实现了两种锁机制,公平锁与非公平锁。

static final class NonfairSync extends Sync 
static final class FairSync extends Sync

使用公平锁或非公平锁是由创建ReentranLock实例时传参指定。

//默认为非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//传true代表使用公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2.分析Lock()与unlock()方法

lock方法是定义在Sync抽象类中的一个抽象方法 ,abstract void lock();,lock方法是请求锁Acquires the lock.我们一般使用如下:

public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
    	reentrantLock.unlock();
    }
//跟入lock()方法-->
 public void lock() {//这个就是ReentrantLock中的lock,可以看到是调用内部抽象类Sync的lock
        sync.lock();
    }

//根据ReentrantLock的构造方法可知,这里实际使用的是非公平锁,所以应该去看NonfairSync类中的lock方法(多态的体现)
final void lock() {
    		//cas 设置state为1(state是调用AbstractQueuedSynchronizer的表示锁状态)
            if (compareAndSetState(0, 1))
                //改状态成功,表示获取到锁,设置持有锁的线程为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);//获取锁失败,调用AbstractQueuedSynchronizer(Sync继承的)中的acquire方法。后面分析。
        }

这是非公平锁的,下面我们看看公平锁的lock方法

final void lock() {
    acquire(1);//直接调用调用AbstractQueuedSynchronizer的中的acquire方法
}

unlock方法:非公平锁和公平锁的unlock方法一样

public void unlock() {
    sync.release(1);//调用AbstractQueuedSynchronizer中的release方法 后面分析
}

我们可以看到,加锁解锁最终实现的关键代码基本都在AbstractQueuedSynchronizer类中,所以说AQS是并发编程的核心。

3.AbstractQueuedSynchronizer(AQS)解析

这个类是并发编程的核心,很多并发的实现都是由他来完成的。这里我们只介绍与ReentrantLock相关部分。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer

3.1AbstractQueuedSynchronizer类结构

内部类Node:

static final class Node {
    	//标记表示节点处于共享模式
        static final Node SHARED = new Node();
        //标记表示节点处于独占(排他)模式
        static final Node EXCLUSIVE = null;
		
    //这两个是我们今天ReentrantLock加解锁可以看到的
        //waitStatus值,表示线程已取消
        static final int CANCELLED =  1;
        //waitStatus值,表示后续线程需要唤醒
        static final int SIGNAL    = -1;
    
    
        //waitStatus值,表示线程正在等待条件
        static final int CONDITION = -2;
        //waitStatus值,表示下一个acquireShared应无条件传播
        static final int PROPAGATE = -3;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;
		
        volatile Thread thread;


        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        //获取前驱节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    //无参构造
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
}

一些成员变量说明:

//等待队列的头,延迟初始化。除初始化外,只能通过方法setHead修改。注意:如果head存在,保证其waitStatus不被取消
private transient volatile Node head;

//等待队列的尾部,延迟初始化。仅通过方法enq修改以添加新的等待节点。
private transient volatile Node tail;
//同步状态 可以理解为锁,0代表无锁
private volatile int state;

下面我们来看看一些关键方法的代码:

3.2 关键方法

3.2.1 加锁过程

首先我们看看加锁时调用的acquire方法

public final void acquire(int arg) {//arg == 1 ,公平锁非公平锁都是传1可以看前面的调用。
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

第一步调用tryAcquire(arg)方法

//tryAcquire这个方法被重写了,我们需要去看重写的方法,首先是公平锁的方法(在FairSyn类中)
protected final boolean tryAcquire(int acquires) {
    		//获取当前线程
            final Thread current = Thread.currentThread();
   			//获得同步状态(锁状态)
            int c = getState();
            if (c == 0) {//没有线程占用锁,state == 0
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    //获取到锁时,设置占有锁的线程为当前线程 
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//当前线程就是持有锁的线程
                int nextc = c + acquires;// state+1 这里就是可重入锁的概念
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//设置锁状态
                return true;
            }
            return false;//没有获取到锁
        }
//head==tail表示未初始化,此时没有等待(之前注释的延迟初始化)没有线程等待返回false
//head!=tail表示已经初始话执行&&后表达式
//(s = h.next) == null 判断head的下一个节点是否存在(真正开始排队的节点),条件成立时表示没有等待线程。
//条件不成立执行 s.thread != Thread.currentThread(),判断等待线程是不是当前线程,不是返回ture,是返回false
// 我们可以看出返回false的均表示不需要等待,因为外层调用时!hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
//CAS改变锁状态
protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
//AbstractOwnableSynchronizer(AQS继承的抽象类)中方法
protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

//非公平锁实现
 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
//基本与公平锁实现一致,只是当没有线程持有锁(state==0)时,不需要查看队列,直接CAS加锁
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;
        }

//获取锁失败进入第二步进行队列相关操作

//维护队列
private Node addWaiter(Node mode) {//mode Node.EXCLUSIVE==null
    Node node = new Node(Thread.currentThread(), mode);//构造新节点,保存当前线程
    // 记录尾节点
    Node pred = tail;
    if (pred != null) {//有尾节点,表示有线程在等待队列
        node.prev = pred;//入队将当前节点的前驱节点指向尾节点
        if (compareAndSetTail(pred, node)) {//CAS设置当前线程节点为尾节点
            pred.next = node;//前驱节点next指向当前线程节点
            return node;//返回当前节点
        }
    }
    enq(node);//队列未初始化进入(无线程等待)
    return node;
}

private Node enq(final Node node) {
        for (;;) {//死循环是为了保证初始化后将node加入队列,和保证CAS成功。
            Node t = tail;//记录尾节点
            if (t == null) { // 未初始化
                if (compareAndSetHead(new Node()))//CAS设置头结点(初始化队列)
                    tail = head;
            } else {//维护队列 与addWaiter相似
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
//以独占不中断模式获取已在中的线程队列,用于条件等待方法以及获取。
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {//这里死循环是unpark唤醒后,再次去执行获取锁。。。
            final Node p = node.predecessor();//获取当前线程的前驱节点
            //如果p是head节点表示node是第一个排队节点,则再次尝试获取锁
            if (p == head && tryAcquire(arg)) {//p是头结点,并且获取到锁了(此时有线程释放了锁)
                setHead(node);//移动指向头结点的引用,看下方方法解释。
                p.next = null; // help GC  (断掉另一条引用,此时老头结点就没有引用了,可以被回收)
                failed = false;//成功了,所以不失败(废话)
                return interrupted;
            }
            //注意这里只有P不是头结点情况执行(前面已经有线程排队了)
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())//获取锁失败可以park,去park当前线程
                interrupted = true;
        }
    } finally {//代码抛异常执行
        if (failed)//正常情况不会,不做分析,属于异常处理机制。
            cancelAcquire(node);
    }
}

private void setHead(Node node) {
        head = node;//将head节点指向当前node
        node.thread = null;//因为线程已经获取了锁,持有锁的线程不参与排队,(头结点线程永远为null)
        node.prev = null;//断开新头结点指向老头结点的引用(双向链表,这里只断了一条)
    }

//检查并更新未能获取锁的节点的状态。如果线程阻塞,则返回true。这里是观察前一个节点的状态来判断自己是否需要排队
//这是AQS队列的设计理念,有点像银行取钱,并且你只能看到你前面一个人。这个ws就是标志他的状态,通过观察前一个的状态,判断自己应该怎么做。ws==-1说明前面的人也在排队,所以你也得排队。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; //ws第一次进入是0,int型初始化值(外部调用函数在循环)
        if (ws == Node.SIGNAL)//Node.SIGNAL==-1
           //表示(当前线程节点前一个节点)已设置状态,前面的在等待,因此它可以安全地park
            return true;
        if (ws > 0) {//条件队列的,不做具体分析
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //CAS设置pred节点的值为-1,
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//调用后当前线程就停在这里(想象debug停在这里)
        return Thread.interrupted();//检查是否中断
    }

到这里lock加锁过程已经分析完了,这里有总结的一张流程图,帮助理解和分析。

ReentrantLock加锁流程 by 考拉2020

3.2.1 解锁过程

相对于加锁方法解锁就比较简单了

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {//这个方法多个类重写了,找ReentrantLock里的方法,如下
        Node h = head;
        if (h != null && h.waitStatus != 0)//头结点不为空且它的ws不等于0(队列里还有等待的)
            unparkSuccessor(h);//唤醒线程
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//拿到当前的锁状态(同步状态) - 1(锁重入,逐级解锁)
    //判断当前线程是不是拥有锁的线程,不是的话就抛异常。
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//最后一层锁,解锁完成,设置持有锁的线程为空
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);//设置锁状态
    return free;
}
private void unparkSuccessor(Node node) {
    //注意这里是当前头节点的ws
    int ws = node.waitStatus;
    if (ws < 0)//如果状态为负,清除锁状态
        compareAndSetWaitStatus(node, ws, 0);

   //unpark的线程保存在后续节点中,通常只是下一个节点。但如果取消或明显为空,
   //则从尾部向前遍历以找到实际的未取消的后续节点
    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);//唤醒线程
}

下面是解锁过程的流程图:
ReentrantLock解锁流程by考拉2020

3.3 AQS队列加锁过程详解

由于多线程过程中场景比较复杂,不可能每一种情况都分析,这里只分析两种最基础的,基本可以覆盖源码,这里建议打开源码,对照源码,或者上面加锁过程流程图,进入队列部分进行阅读,充分理解AQS队列。

3.3.1 场景一

场景1:持有锁的线程为t1,当前线程为t2(获得cpu),t2未获取到锁,调用addWaiter,

(step1):创建node2。此时队列未初始化,调用enq(node2).队列未初始化,tail==null,

(step2):调用compareAndSetHead(new Node())(创建node1并设置为head)。

(step3):tail = head;此时队列初始化完成,队列中有一个节点。由于在循环中,进行下一次循环。

(step4):此时tail != null,node.prev = t;

(step5):CAS设置node2为新的tail,compareAndSetTail(t, node),

(step6):将node1.next指向node2,t.next = node;(t是一个临时变量。每一次循环开始赋值,现在t是node1),返回node2

(step7): acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),node2.prev是head结点,但此时线程t1持有锁未释放,所以进入判断shouldParkAfterFailedAcquire(p, node),node2.prev.ws==0,CAS设置其为-1.进入下一次循环,再次进入本方法。

(step8):node2.prev.ws==-1,返回true进入parkAndCheckInterrupt(),调用LockSupport.park(this);将当前线程t2停住。

AQS加锁场景一by考拉2020

3.3.2 场景二

场景二:持有锁的线程为t1,当前线程为t3,aqs队列里t2在排队(注意这里面有两个节点,头结点是没有线程的,持有锁的线程不参与排队)。

(step1):获取锁失败,调用addWaiter(Node.EXCLUSIVE), arg),创建新节点node3.

(step2):tail != null,将node3.prev指向当前尾节点node2.

(step3):CAS设置当前节点node3为尾节点。

(step4):pred.next = node;(node2.next =node3),返回node3.

(step5):acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),此时node3.prev != head。调用shouldParkAfterFailedAcquire(p, node),两次循环将node2.ws设置为-1并返回true。

(step6):进入parkAndCheckInterrupt(),调用LockSupport.park(this);,将当前线程t3停住。

(注:绿色为新增操作,虚线部分是原本有的引用改动后断开了)

AQS加锁场景二by考拉2020

未完待续…
(如有错误欢迎各位同学评论留言指正)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值