并发编程之ReentrantLock AQS原理

简单介绍

ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字相比,它更灵活、更强大,增加了轮询、超时、中断等高级功能。ReentrantLock,顾名思义,它是支持可重入锁的锁,是一种递归无阻塞的同步机制。除此之外,该锁还支持获取锁时的公平和非公平选择。
ReentrantLock底层基于AQS实现锁机制。
ReentrantLock中存在抽象类Sync,有两个实现类FairSync公平锁和NonfairSync非公平锁。
AQS全名AbstractQueuedSynchronizer,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。

ReentrantLock常用api

//无参构造默认生成NonfairSync即非公平锁
public ReentrantLock();
//带参构造fair true为FairSync,false为NonfairSync
public ReentrantLock(boolean fair);
//加锁操作,加锁失败会阻塞,底层使用LockSupport.park()阻塞线程
public void lock();
//尝试加锁操作,不会阻塞,返回成功失败
public boolean tryLock();
//允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待
//而直接返回,这时不用获取锁,而会抛出一个InterruptedException
public void lockInterruptibly() throws InterruptedException;
//解锁操作,底层使用 LockSupport.unpark()唤醒park()线程
public void unlock() ;

AQS基本结构

//AQS内部维护双向队列的基本结构
public abstract class AbstractQueuedSynchronizer{
	//队列的头节点
    private transient volatile Node head;
	//队列的尾节点
    private transient volatile Node tail;
	//队列的状态
    //State == 0 表示没有线程拿到锁
    //State == 1 表示某个线程拿到锁
    //State > 1 表示可重入锁,某个上多个锁
    private volatile int state;
	static final class Node {
			//上一个Node 
	        volatile Node prev;
			//下一个Node 
	        volatile Node next;
			//Node的线程
	        volatile Thread thread;
	        //Node中维护的下一个等待Node 
	        Node nextWaiter;
	        //等待状态
	        volatile int waitStatus;
	}
}

大体如下图:
在这里插入图片描述
此处先说明下,aqs的队首元素不参与排队,对比火车站排队买票,排在首位的售票员正在给他处理业务,首位的下一位才是排队的第一位。后续源码会给出分析验证此处观点。

ReentrantLock公平锁加锁操作源码解析

lock unlock简单使用

		//初始化ReentrantLock,此处使用公平锁
        Lock lock = new ReentrantLock(true);
        try{
        	//加锁操作
            lock.lock();
        }finally {
            //解锁操作
            lock.unlock();
        }

FairSync.lock公平锁上锁操作

现在开始我们ReentrantLock公平锁的加锁源码解析

		//FairSync类lock方法
        final void lock() {
        	//调用AbstractQueuedSynchronizer类acquire方法
        	//arg写死1
            acquire(1);
        }

acquire获取锁

    public final void acquire(int arg) {
 		 //tryAcquire 尝试获取锁
        //当tryAcquire返回false,表示获取锁失败
        //会尝试添加到队列中acquireQueued
        //addWaiter往aqs队列中添加Node
        //acquireQueued会尝试再次获取,因为过程中持有锁的线程可能会释放锁
        //最后获取不到会park阻塞线程
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
     //当前线程中断操作
     static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

tryAcquire 尝试获取锁

第一个重点来,tryAcquire 方法

 protected final boolean tryAcquire(int acquires) {
 			//获取当前线程
            final Thread current = Thread.currentThread();
            //获取锁状态
            //State == 0 表示没有线程拿到锁
            //State == 1 表示某个线程拿到锁
            //State > 1 表示可重入锁,某个上多个锁
            int c = getState();
            if (c == 0) {
            	//hasQueuedPredecessors是区别于公平锁和非公平锁的本质区别
            	//非公平锁不会调用此方法,锁空闲时线程一起竞争
            	//公平锁调用此方法,判断是否需要排队 
            	//	返回false表示不需排队,cas竞争锁
            	//	返回true表示需要排队,走后续逻辑
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    //cas竞争,成功后设置aqs持有锁的线程为当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //此时c > 0表示锁已被某线程持有
            //判断持有锁的线程是否为当前线程,true表示可重入
            //state+1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //其他情况返回false
            return false;
        }

先看下hasQueuedPredecessors方法,很简短但很强悍

    public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
         //未初始化时h t都为空,h != t返回false,不需要排队
        //队列有值时,队列只有一个节点,首尾相同,false,不需要排队
        //	队列有多个节点,首尾不同,返回true,继续后续 && 判断
        //  	h.next == null true表示队列只有一个节点,方法返回true
        //      false 将头节点的下一个节点赋值给s,判断s是否等于当前线程
        //      	s == 当前线程 表示重入
        //          s != 当前线程 表示重入
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

总结下3种情况不需排队
1、aqs队列未初始化,h t都为null
2、aqs队列只存在一个元素,执行到最后一个时会存在一个元素
3、第二个节点=当前线程,表示可重入

此处有个问题,为何判断第二个节点等于当前线程而非队首节点?
aqs认为队列第一次不需要排队,要么是拿到锁的线程,要么虚拟的线程。
假如现在有t1 t2两个线程,队列没有初始化时,t1进来 h != t 直接返回false,拿到锁,没有初始化队列。假设t1拿锁时,t2进来,初始化队列,此刻t1持有锁,队列首位默认虚拟节点(aqs的head指向虚拟节点,thread = null),next指向t2。等t1释放锁,t2拿到锁,则t2为对首元素,aqs的head指向t2的Node,t2的Node的thread设为null。所以队首默认为持有当前锁的线程,不参与排队,且thread永远==null。

addWaiter向aqs队列添加原始

       private Node addWaiter(Node mode) {
       //封装Node
        Node node = new Node(Thread.currentThread(), mode);
        //队列尾节点赋值给pred
        Node pred = tail;
        if (pred != null) {
        	//添加操作,因为aqs是双向队列,添加操作只是维护pred和next指向
            //尾节点不为空,赋值给node节点的上一个
            node.prev = pred;
            //cas将node节点写入aqs内存中,成功把node赋值给之前的尾节点next
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //初始化队列,添加Node
        enq(node);
        return node;
    }
    
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //尾节点为空需初始化
            if (t == null) {
                //cas设置头节点,自璇成功进入else,
                //注意此处为new node,表示设置头节点是虚拟节点
                if (compareAndSetHead(new Node()))
                    //tail和head都为虚拟节点
                    tail = head;
            } else {
                //维护队列,排在队列对尾后一个
                node.prev = t;
                //继续自璇cas,成功返回
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

enq执行步骤:
1、首次循环,判断队列尾节点是否为null,是null则初始化队列
2、设置头节点为new Node(),即虚拟Node放入
3、cas设置头节点成功,将头节点赋值给尾节点,即头尾都为虚拟Node
4、再次循环,由于此次队列尾节点不为null,排队node的上一个节点指向虚拟Node
5、cas设置虚拟Node的下一个节点指向排队node,成功返回

acquireQueued获得队列

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                final Node p = node.predecessor();
                //上个节点为首节点,表示当前node为排队中的第一个节点,尝试获取锁
                //获取成功设置node为首节点,
                //node.thread = null;
                //node.prev = null;
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    //之前队首p.next=null,p无GC ROOT关联,gc可回收
                    p.next = null;
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt()) //park线程
                    interrupted = true;
            }
        } finally {
        	//失败时取消队列
        	//node.waitStatus = Node.CANCELLED;
        	//主要讲node的waitStatus 设为 1 ,表示取消状态
        	//解锁时不会unpark waitStatus >0的队列原始
            if (failed)
                cancelAcquire(node);
        }
    }

acquireQueued执行过程:
1、当前node上一个节点,如果是队首,表示当前node是排队中的第一个节点
2、则去尝试获取锁,若获取成功设置当前node为队首,之前的队首移除队列执行,可GC
3、不是排队中的第一个节点或者获取锁失败,进去shouldParkAfterFailedAcquire方法
4、所有node初始化时ws 都为0,进去会cas设置上一node的ws的状态为-1
5、再次循环走 1 2 3 操作
6、若再次进去shouldParkAfterFailedAcquire方法,上一node的ws的状态已为-1,返回true
7、则会进去parkAndCheckInterrupt方法,这方法比较简单,调用LockSupport.park()
8、当前线程阻塞,直至unlock()调用LockSupport.unpark(当前线程)才会继续后续执行

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //取当前node上一节点的ws
        //aqs中上一个node管理下一个node的ws状态
        //因为当前node后续会park阻塞操作,不能有任何操作
        //解锁过程需要下一个node的ws状态
        //类比睡着的人不知道自己睡着没有,需要别人管理他的睡眠状态
        //别人看这个人睡着了,关上门,贴个睡着牌子
        int ws = pred.waitStatus;
        //SIGNAL为-1
        //1、判断ws为-1 返回 true,pred初始化时为0,首次不会进来
        //4、自旋继续到此,上次已设置为-1,会进来,返回true
        if (ws == Node.SIGNAL)
            return true;
		//2、pred首次为0不会进来
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
             //3、设置pred.waitStatus为-1
            //-1表示线程park,正在睡眠            
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    private final boolean parkAndCheckInterrupt() {
    	//park阻塞线程
        LockSupport.park(this);
        //为何需要执行这一段操作很操蛋,看的很模糊,下面结合方法统一说明下
        return Thread.interrupted();
    }

selfInterrupt操作

解释下selfInterrupt操作,本人看这段操作很迷,我们先看下流程,!tryAcquire(arg)获取锁失败则acquireQueued方法中parkAndCheckInterrupt因为调用了Thread.interrupted()方法,会返回true,所以会执行Thread.currentThread().interrupt()操作。
先说明下Thread.interrupted()方法,用户执行interrupt()操作后,Thread.interrupted()首次执行会返回true,再次执行会返回false,即操作一次使其恢复默认false。
那么为了不改变用户行为,在parkAndCheckInterrupt中因为调用了次Thread.interrupted(),如果返回true表示用户执行过interrupt()操作,所以要在外面还原回来,再次执行下interrupt操作。
是不是很迷,有种和空气斗智斗勇的感觉,你直接不做判断不就啥事没有,因为Thread.interrupted()的状态返回值代码中没有任何用处,很蛋疼?
后来看了lockInterruptibly方法,最后调用doAcquireInterruptibly方法,是不是和acquireQueued很相似,只是这边parkAndCheckInterrupt返回true会抛异常,所以作者应该是复用代码,所以上面迷一般的操作是为了兼容复用,很蛋疼。

private void doAcquireInterruptibly(int arg)
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

ReentrantLock非公平锁加锁操作源码解析

区别于公平锁在于下面两个方法

    final void lock() {
    	//进去lock方法时所有线程去cas竞争锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
        	//竞争失败调用下面的nonfairTryAcquire方法
        	//acquire中调用nonfairTryAcquire
            acquire(1);
    }
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;
    }

ReentrantLock解锁操作源码解析

ReentrantLock.unlock解锁

    public void unlock() {
        sync.release(1);
    }

release释放锁

	//释放锁
    public final boolean release(int arg) {
    	//尝试释放操作
        if (tryRelease(arg)) {
        	//释放成功,则判读aqs头节点不为空且waitStatus不为0
        	//之前lock操作已说明,h.waitStatus表示下一个node的
            Node h = head;
        	//waitStatus = 0表示初始状态,park操作前会修改为 -1,需唤醒
            if (h != null && h.waitStatus != 0)
            	//执行unpark操作,唤醒线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease尝试释放锁

 protected final boolean tryRelease(int releases) {
 			//aqs的state - 1
 			//state = 1 表示当前线程持有锁
 			//state > 1 表示当前线程重入锁
            int c = getState() - releases;
            //判断线程是否是否为锁的持有线程,一般都为true
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //c == 0表示已释放锁,线程free空闲,持有锁线程为null
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //重新设置state值
            setState(c);
            return free;
        }

unparkSuccessor唤醒线程

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        //判断下一个node的waitStatus<0 ,cas设为0
        //进来此方法一般都为-1
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //取出下一个node,判断不为空或者waitStatus>0
        //两者有一个为true表示下一个node要么为空,要么是取消状态,不需唤醒    
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            //循环下面的node节点,取出waitStatus<0的node
            //表示此node的下一个node为有效需唤醒节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //判断s不为null,则执行unpark唤醒操作
        if (s != null)
            LockSupport.unpark(s.thread);
    }

心得

扒了ReentrantLock和AQS代码,真的佩服Doug Lea,大写的牛逼。
希望我写的内容能对读者有作用,可以对比注释逐行过下代码,多看两遍会有不一样的心得。
当然我写的也不一定都对,只是个人对ReentrantLock和AQS代码的理解,如有错误的地方,麻烦指出。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值