ReentrantLock锁原理,源码解析,AQS

前言

ReentrantLock基于aqs实现,而AQS里面维护了一条双向链表及当前node的状态,相对于synchronized来说,ReentrantLock的api更丰富,包含公平锁,非公平锁,条件阻塞等,今天我们就来讲解一下ReentrantLock锁的实现

ReentrantLock创建方式

/**
     * 默认构造器,创建非公平锁
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * 由fair指定,true代表公平锁,false代表非公平锁
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

看构造方法,创建这个对象的时候实际上是给sync创建对象,那么sync又是什么呢,下面是他的类图
sync类图
看了类图,我们直到为什么ReentrantLock是基于AQS实现,它创建时其实是创建Sync的实现类,而Sync又是继承AbstractQueuedSynchronizer,我们简称为AQS
然后我们看他们的执行流程,然后再开始讲代码,我们以公平锁为例,下面是我自己画的流程图

公平锁大致流程
公平锁上锁大致流程是这样,接下来根据源码进行讲解
首先我们先创建一个锁

Lock lock=new ReentrantLock();
lock.lock();
lock.unlock();

lock()

我们先进入lock.lock()操作,先看一下里面的操作,里面的方法就只有一个,我们选择FairSync实现类的方法,到acquire(1)方法

public final void acquire(int arg) {
		//tryAcquire(arg) 尝试获取锁
        if (!tryAcquire(arg) &&
        	//如果获取不到锁,则进入入队操作
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire(arg)尝试获取锁

然后我们进入tryAcquire(arg)方法,看如何尝试获取锁

/**
*尝试获取锁
*/
protected final boolean tryAcquire(int acquires) {
			//获取当前线程
            final Thread current = Thread.currentThread();
            //获取锁状态,0代表目前没有线程持有锁
            int c = getState();
            //如果当前没有线程持有锁
            if (c == 0) {
            	//如果没有线程持有锁,进入判断,下面会讲到
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    //如果cas成功,则将当前占有锁线程置为当前线程
                    setExclusiveOwnerThread(current);
                    return 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;
            }
            return false;
        }
    }

!hasQueuedPredecessors()

public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        //h != t 先判断这个队列中的头指针是否等于尾指针,如果等于,则代表当前还没被初始化,队列为null
        return h != t &&
        	//1.(s = h.next) == null
        	//  判断头指针的下个指针是否为null
        	//  什么时候 h != t && (s = h.next) == null
        	//  进入初始化过程(enq(node)方法,下面会讲到),tail=head还没被执行,但是head已经被赋值
        	//2.s.thread != Thread.currentThread()
        	//  判断当前要占用锁的线程,队列中的下个节点的线程
        	//  其实时为了acquireQueued()之后tryAcquire(arg)准备
        	//  线程第一次进来,这个判断肯定为true,这里我们先记住这个操作,后面我会讲到
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

acquireQueued()方法主要进行park和设置头节点和尝试占用锁操作,我们理解为出队操作
addWaiter()进行入队操作

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
        //判断队列为不为null,如果不为空则尝试进行入队操作
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //cas入队操作失败或者队列未初始化
        enq(node);
        return node;
    }

enq(node)方法

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果尾节点为null,则进行cas初始化头节点操作,操作完之后
            //还没进入到tail=head这个操作
            //此时新来一个线程尝试获取锁
            //对应上面 h != t && (s = h.next) == null这个判断
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	//设置完头节点和尾节点之后,再次循环进来
            	//将当前线程进行入队操作,头节点代表占有锁线程
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

此时调用addWaiter()方法完毕,返回当前线程的节点信息,然后调用acquireQueued(final Node node, int arg)方法

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //流程图的死循环2
            for (;;) {
            	//node.predecessor();获取node的上个节点
                final Node p = node.predecessor();
                //如果上个节点为头节点
                //那么尝试获取一下锁
                //这里的尝试获取锁,调用上面tryAcquire(arg)的hasQueuedPredecessors()方法
                //此时的这个判断 s.thread != Thread.currentThread()此时的这个判断是为false
                //代表即将要获取锁的线程和当前相乘相等,并且在外面的判断是!hasQueuedPredecessors()
                //所以进入获取锁操作
                //获取锁结束完,设置头节点为当前节点
                //然后setHead()方法中会将弄得的线程设置为null,然后prev设置为null
                //再将之前头节点next指针设置为null,方法jvm  gc,返回中断状态
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //shouldParkAfterFailedAcquire  判断p.waitStatus状态是否是阻塞状态
                //0 将waitStatus改为-1,循环一次
                //然后判断是否为负数,如果是,则返回true
                //这两个方法下面简单描述一下
                if (shouldParkAfterFailedAcquire(p, node) &&
                	//阻塞线程操作,返回线程是否被中断
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire(p, node)方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //获取上个节点的等待状态,如果是-1,则进行阻塞
            return true;
        if (ws > 0) {
            //上个节点如果是取消状态,则将上个节点移除
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //获取上个节点的等待状态,如果是-1,则放回false,再让外面的方法进行一次循环
            //个人认为这里是比较体现乐观锁思想的一个地方
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt()方法

private final boolean parkAndCheckInterrupt() {
		//阻塞线程
        LockSupport.park(this);
        //返回线程是否被中断,在其他地方调用该方法的时候,会抛出异常,例如doAcquireInterruptibly()
        //然后重置中断状态
        return Thread.interrupted();
    }

由此,线程阻塞

  1. 调用LockSupport.unpark(Thread );方法或者interrupt();方法,线程取消阻塞
  2. 然后再次循环,尝试获取锁,当获取到锁时,整个方法返回
  3. 如果acquire(1)中的判断为true,说明线程是被中断阻塞的,所以要将中断标记重新打上
  4. 如果为false,说明线程是通过LockSupport.unpark(Thread)解除阻塞,直接返回即可
  5. 到此,线程获取锁成功,这就是ReentrantLock获取锁流程

上锁总结

其实Lock锁都会一次次的去循环尝试获取锁,总是乐观的认为当前线程能获取到锁,直到最后阻塞,这就是乐观锁的思想

unlock()方法解锁

unlock方法相对加锁流程更容易理解

//unlock调用该方法释放锁
public final boolean release(int arg) {
		//尝试释放锁
        if (tryRelease(arg)) {
            Node h = head;
            //头指针不为空,代表有队列
            //头指针的等待状态不等于0,代表下个节点被阻塞
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease方法

protected final boolean tryRelease(int releases) {
			//state-1 ,直到c=0
			//如果c!=0,代表重入锁,当前线程继续占有锁
            int c = getState() - releases;
            //释放锁的时候,必须确定该线程已经被加锁,不然会报异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //c==0,则将占有锁的线程释放
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

unparkSuccessor()方法

//尝试释放锁成功,调用该方法
private void unparkSuccessor(Node node) {
		//获取当前等待状态
        int ws = node.waitStatus;
        if (ws < 0)
        	//<0将等待状态通过cas置为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;
        }
        //如果头指针的下个节点不等于null,则将该线程unpark,取消阻塞
        if (s != null)
            LockSupport.unpark(s.thread);
    }

到此,线程解锁成功
讲了公平锁,非公平锁大家感兴趣可以去看一下源码,其实流程差不多,也是用双向队列来维护阻塞的线程,但是当头指针释放锁的时候,新进来的线程会和队列中线程竞争这把锁,来达到非公平的效果

最后,如果上述代码或流程图有错的地方,请大家评论留言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值