美文网首页
1.8ReentrantLock史上最详细源码分析

1.8ReentrantLock史上最详细源码分析

作者: IT界刘德华 | 来源:发表于2019-11-02 21:35 被阅读0次

    前言

    ReentrantLock是基于AQS实现的可重入的独占锁,当然它可以实现公平锁和非公平锁,下面会一一进行讲解。这里一些AQS的知识就不过多的介绍,详细见我的AQS篇章https://www.jianshu.com/p/54d372425e54

    类结构

    public class ReentrantLock implements Lock, java.io.Serializable {
        //内部类持有对象,是公平锁还是非公平锁,该对象继承了AbstractQueuedSynchronizer 
        private final Sync sync;
    
        //默认初始化是非公平锁
        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
        //传参true知道公平锁
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    
        //继承AQS框架
        abstract static class Sync extends AbstractQueuedSynchronizer {
    
        }
    
        //非公平锁
        static final class NonfairSync extends Sync {}
    
        //公平锁
        static final class FairSync extends Sync {}
    }
    

    源码分析

    NonfairSync(非公平锁分析)
    • lock加锁,如果获得锁将重入状态和数量改下,并设置独占线程为当前线程,否则继续获取资源
    final void lock() {
                //状态是0代表可以获取锁,如果内存中是0就获取锁,并cas改成1表示得到锁重入次数是1
                if (compareAndSetState(0, 1))
                    //得到锁则将当前线程往爷爷类AbstractOwnableSynchronizer设置为拥有独占锁的线程
                    setExclusiveOwnerThread(Thread.currentThread());
                //没获得锁
                else
                    //请求获得资源
                    acquire(1);
            }
    
    • compareAndSetState(cas比较状态更新状态值,达到加锁的目的)
    protected final boolean compareAndSetState(int expect, int update) {
            // AbstractQueuedSynchronizer对象+状态偏移量找到内存的状态值
            //如果内存的状态值和expect期望值相等,则用update更新内存的状态值
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }
    
    //获取资源,如果tryAcquire没获取到资源,则acquireQueued从等待队列自旋去获取资源
    //如果依然没获取到则进入阻塞状态,当然如果acquireQueued在判断取到的线程已经被中断
    //则调用selfInterrupt去打断线程
    //参数arg,该值会传送到{@link #tryAcquire},但否则不会被解释,可以代表任何内容,如reentrantLock传的是1。
    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    
    • tryAcquire(NonfairSync非公平锁对其进行重写),尝试获取资源
    protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
    
    • nonfairTryAcquire非公平锁获取,如果失败就返回false,如果锁还未被获取则设置独占线程,改重入次数,返回获锁成功,若果锁已经被获取,则累计重入次数设置为新的重入次数,返回获锁成功
    //在sync内部的方法
    //非公平锁获取资源
    final boolean nonfairTryAcquire(int acquires) {
                //当前线程
                final Thread current = Thread.currentThread();
                //获取父类AbstractQueuedSynchronizer的计数值
                int c = getState();
                //锁还未被获取
                if (c == 0) {
                    //acquires=1传进来的,cas设置state的状态,改为已经被当前线程获得锁,并标记重入次数
                    if (compareAndSetState(0, acquires)) {
                        //设置当前线程为当前锁的独占线程
                        setExclusiveOwnerThread(current);
                        //获得锁成功
                        return true;
                    }
                }
                //状态!=0s说明已经被获得锁
                //如果获得锁的独占线程是当前线程则可以继续获得锁
                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;
            }
    
    FairSync(公平锁分析)
    • lock加锁,如果获得锁将重入状态和数量改下,并设置独占线程为当前线程,否则继续获取资源。公平锁的lock少了设置重入次数和独占线程设置,而是自己调acquire直接获取资源
    final void lock() {
                acquire(1);
            }
    
    • acquire就是AQS框架讲到的了,这里不做太多的解释,详见AQS篇,这里只会对tryAcquire重新进行讲解
    public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
    
    • tryAcquire(尝试获取公平锁)
    //尝试获取公平锁
    protected final boolean tryAcquire(int acquires) {
                //当前线程
                final Thread current = Thread.currentThread();
                //锁的重入次数
                int c = getState();
                //锁还未被获取
                if (c == 0) {
                    //hasQueuedPredecessors判断队列是否还有等待时间长的线程,也就是排在队列前的
                    //如果没有则比较设置重入值,获锁成功设置当前线程为锁的独占线程返回true
                    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(查询是否有任何线程等待获取的时间比当前线程长)
      判断等待队列里不止一个节点,队列里的下一个指针是空,则返回true,代表已经有等待节点
      判断等待队列里不止一个节点,队列里的下一个指针不是空,则要判断头结点的下一个节点是不是当前线程,如果是则返回false,如果不是则返回true
    //查询是否有任何线程等待获取的时间比当前线程长
    //如果h==t成立,h和t均为null或是同一个具体的节点,无后继节点,返回false。
    //如果h!=t成立,head.next是否为null,如果为null,返回true。什么情况下h!=t的同时h.next==null??,
    //有其他线程第一次正在入队时,可能会出现。见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail=head语句时,
    //此时tail=null,head=newNode,head.next=null。
    //如果h!=t成立,head.next != null,则判断head.next是否是当前线程,如果是返回false,
    //否则返回true(head节点是获取到锁的节点,但是任意时刻head节点可能占用着锁,也可能释放了锁(unlock()),
    //未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)
    public final boolean hasQueuedPredecessors() {
          
            Node t = tail; 
            Node h = head;
            Node s;
            return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
        }
    
    tryLock(其实调用的就是Sync类的nonfairTryAcquire非公平获取资源方法)

    没获取到锁就返回了不会跟lock一样还要一直阻塞

    public boolean tryLock() {
            return sync.nonfairTryAcquire(1);
        }
    
    unlock(解锁)

    调用的是AQS类的release方法

    public void unlock() {
            sync.release(1);
        }
    
    • AQS类的release方法,该方法的tryRelease是在ReentrantLock内部类Sync重新的
      一般在老二获得资源后就会将自己设置为head,所以在释放资源的时候用head开始释放资源,并唤醒老二(后继节点)
    //释放资源
    public final boolean release(int arg) {
        //将重入次数减到0后释放锁成功
        if (tryRelease(arg)) {
            //头结点
            Node h = head;
            //头结点不是空并且它的状态不是初始化阶段,如果是<0的状态会将当前节点置为0
            //并且找到有效的后继节点进行释放
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            //释放成功
            return true;
        }
        //释放失败
        return false;
    }
    
    • tryRelease
      试释放锁,releases释放次数,因为锁是可重入的,上了几次锁就要解锁几次
    //尝试释放锁,releases释放次数,因为锁是可重入的,上了几次锁就要解锁几次
            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;
            }
    

    有些方法没去讲,因为是调用AQS的方法,已经说过就不讲了
    AQS详见我的AQS源码解析篇https://www.jianshu.com/p/54d372425e54
    觉得还可以的话给我点点赞呀!!!!!

    相关文章

      网友评论

          本文标题:1.8ReentrantLock史上最详细源码分析

          本文链接:https://www.haomeiwen.com/subject/vdftbctx.html