ReentrantLock加锁与解锁源码解析

27 篇文章 0 订阅
4 篇文章 0 订阅

我们使用ReentrantLock的时候一般是如下用法

public class MainActivity  {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try{
          //逻辑代码
        }finally {
            lock.unlock();
        }
    }
}

今天来看看其实现,首先从构造函数看起,主要有两个构造函数

    public ReentrantLock() { //默认构造函数,创建的是非公平锁
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {//带布尔值的构造函数,可以选择创建公平锁还是非公平锁
        sync = fair ? new FairSync() : new NonfairSync();
    }

正常来说我们会先调用ReentrantLock.lock();接下来看其代码实现

    public void lock() {
        sync.lock();
    }

可以看到上述代码调用了sync的lock方法,那么这个sync是什么呢?代码如下

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
       
        abstract void lock();

        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;
        }
        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;
        }
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
        final boolean isLocked() {
            return getState() != 0;
        }
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

从上述代码中可以得知,sync是一个抽象内部类,至于这个类的逻辑是什么,这里先跳过。我们先找这个抽象内部类的实现类。 这个抽象类的实现类有两种,一种是非公平锁,一种是公平锁,在这里我们只关注非公平锁的实现,代码如下

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

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

代码追到这里,我们也就发现了调用ReentrantLock.lock();其实到最后调用的是NonfairSync(非公平锁)的lock方法。接下来看看非公平锁这个lock方法的一些细节。代码如下:

        final void lock() {//这里的if 体现出了非公平锁
            if (compareAndSetState(0, 1))//使用CAS来设置State这个属性的值,从0设置为1,设置成功表示当前线程拥有这个锁
                setExclusiveOwnerThread(Thread.currentThread());//将持有锁的持有者设置为当前线程
            else
                acquire(1);//如果上述CAS设置失败则跑这里
        }

看完上述的代码,大家可能会有的疑问,1、compareAndSetState(0, 1)这个函数的作用是什么?2、state是什么?3、acquire这个函数的作用是什么?接下来按顺序解答大家的疑问。首先看compareAndSetState(0, 1)

  protected final boolean compareAndSetState(int expect, int update) {
        return U.compareAndSwapInt(this, STATE, expect, update);//使用原子操作来更新state的值
    }
    //可能大家有疑惑,这里的U是什么,它的声明如下
    //private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    //是作用就是:JDK 的 rt.jar 包中的 sum.msic.Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是native 方法,它们使用 JNI 的方式访问本地C++ 实现库

看完了第一个疑问,接下来来看第二个问题:state是什么?

private static final long STATE;//用来表示锁状态的,0表示未被加锁,大于0表示已经加锁,已被占用
static {
        try {
            STATE = U.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            HEAD = U.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            TAIL = U.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
        Class<?> ensureLoaded = LockSupport.class;
    }

接下来看第三个问题:acquire函数的作用是什么?代码如下

//第一步
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&// 这里可以看到首先调用了tryAcquire,代码贴在下面(看第二步),tryAcquire如果没获得锁,则return false,取反则为true
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();//如果在执行acquireQueued有人中断过该线程,则会调用selfInterrupt来中断线程
    }
    //第二步 这个函数主要封装了一些加锁的逻辑
    protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//获取state的值
            if (c == 0) {//再次比较是否为0
                if (!hasQueuedPredecessors() && //这个方法相对复杂,下面单独贴出来分析(第三步)
                    compareAndSetState(0, acquires)) {//再次使用CAS尝试加锁,试图将state设置为1
                    setExclusiveOwnerThread(current);//设置当前线程为获取锁的线程,这里是为了实现可重入
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//当state不等于0的时候,也可能是当前线程设置过这个state的状态,所以会加这个判断
                int nextc = c + acquires;//当前线程每重入一次,就会对state的值加1
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);//将更新后的state设置回去
                return true;
            }
            return false;
        }
        
    //第三步
        public final boolean hasQueuedPredecessors() {
            Node t = tail; //尾部节点
            Node h = head;//头部节点
            Node s;
            return h != t && 
            ((s = h.next) == null || 
            s.thread != Thread.currentThread());
    }
    //首先分析hasQueuedPredecessors返回false的情况
    //1、h != t 表示队列存在两个或者以上的元素,并且前两个不等 为true
    //2、(s = h.next) == null 表示头节点的第二个元素不为空,并赋值给节点s 所以为false
    //s.thread != Thread.currentThread() 表示第二个节点是当前线程了 为false
    //hasQueuedPredecessors返回true的情况
    //第1.2步和上述一样,最主要还是第3步判断当前线程是不是第二个节点,是的话就可以去获取锁.
    
    //上述第2、第3步都是从第一步的tryAcquire追出来的,看完tryAcquire接下来还是回
    //到第一步,如果第一步的tryAcquire返回false说明加锁失败,取反后,继续跑下面的逻辑
    //即下面两个函数 acquireQueued(addWaiter(Node.EXCLUSIVE), arg));selfInterrupt();
    //我们要关注的有三个函数:
    //================================
    //函数一 此方法主要封装了以下操作获取锁的线程Node节点会被移出队列,获取失败则会进入阻塞
    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
            //获取当前节点的前驱节点
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {//如果前驱节点是头节点,并且自己已经获得锁
                    //就把当前节点设置为头节点,因为前驱节点已经获得了锁,所以前驱节点不用再留在队列
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                //如果前驱节点不是头节点或者没有获得锁,shouldParkAfterFailedAcquire用于判断当前线程是否需要被阻塞,parkAndCheckInterrupt用于阻塞线程并且检测线程是否被中断
                if (shouldParkAfterFailedAcquire(p, node) &&//这个函数会在下面单独贴出来
                    parkAndCheckInterrupt())//这个函数会在下面单独贴出来
                    interrupted = true;//记录在这个方法执行过程中,是否被中断
                    
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    } 
    //函数二 将当前线程以mode的方式(EXCLUSIVE或者SHARED)构成新节点并入队,返回这个新节点
    private Node addWaiter(Node mode) {
        Node node = new Node(mode);//创建一个新的节点A
        for (;;) {
            Node oldTail = tail;//记录队尾节点
            if (oldTail != null) {
                U.putObject(node, Node.PREV, oldTail);//插入节点至队列尾部
                if (compareAndSetTail(oldTail, node)) {//利用cas操作将队尾指针指向新节点
                    oldTail.next = node;//将旧的队尾节点指向新节点A
                    return node;
                }
            } else { //tail为null,说明队列还没初始化
                //创建一个节点,作为同步队列的第一个元素,head、tail都指向它,然后进入下一个循环
                initializeSyncQueue();
            }
        }
    }
    //函数三
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

shouldParkAfterFailedAcquire函数和parkAndCheckInterrupt函数如下

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取前驱节点的等待状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)  //SINGAL表示后继结点处于等待状态,当前节点释放了锁或者被取消,就会通知后续点去运行,作为后继节点,node直接返回true,表示需要被阻塞
          
            return true;
        if (ws > 0) {
            do {
            //前驱节点被取消了,需要从队列中移除,并且循环找到下一个不是取消状态的节点
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        //通过CAS将前驱节点设置为SINGAL
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//阻塞当前线程
        return Thread.interrupted();//检测该线程是否被中断,该方法会清除标志位
    }

到此加锁的过程已基本结束,接下来从头回看下: 1、首先代码里面调用 lock.lock();—》2、然后就调用到NonfairSync的lock方法。这个方法会先尝试去获取锁 把state设置为1。如果失败则调用acquire方法 ----》3、acquire方法首先会调用tryAcquire方法进行加锁,如果加锁失败调用acquireQueued ----》4、acquireQueued方法的一个参数调用了addWaiter会把当前线程封装成node,并加到队列尾部,并返回当前node节点给acquireQueued----》5、acquireQueued方法会尝试再去获取锁,成功获取锁的线程Node节点会被移出队列,获取失败则会进入阻塞。看完加锁的逻辑,接下来看看解锁的代码

//ReentrantLock里面的unlock方法,可以看到调用了sync的release方法
    public void unlock() {
        sync.release(1);
    }
// 这个release方法,调用的是aqs里面的release
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//调用1 尝试释放锁
            Node h = head; 
            if (h != null && h.waitStatus != 0)
                //头节点已经释放,唤醒后续节点
                unparkSuccessor(h);//调用2
            return true;
        }
        return false;
    }
//调用1
    protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//state减1,因为线程可能重入这个锁不止一次,所以state的值可能已经大于1了
            if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程不等于拥有这个锁的线程,就抛出异常
                throw new IllegalMonitorStateException();
            //是否完全的释放了锁(可重入性)
            boolean free = false;
            if (c == 0) {
            //完全释放了锁
                free = true;
            //独占锁的持有者设置为null
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
//调用2
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;//获取头节点的waitStatus状态
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);//将头节点的waitStatus状态用CAS操作尝试设置为0
        Node s = node.next;//将头节点的下一个节点赋值给s
        //下面这段代码的逻辑,就是不断循环直到获取到头节点后第一个可用节点
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)//这里之所以要从末尾开始遍历,因为prev是可靠的,next是不可靠的,这个跟插入的代码有关系,详情看enq函数。
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
            LockSupport.unpark(s.thread);//对获取到的可用节点进行唤醒
    }
//enq函数如下,这个函数的作用就是插入节点
    private Node enq(Node node) {
        for (;;) {
            Node oldTail = tail;//保存老队尾节点
            if (oldTail != null) {
                U.putObject(node, Node.PREV, oldTail);//将新插入的节点的prev指向老队尾节点
                if (compareAndSetTail(oldTail, node)) {//将队尾节点更新为新插入节点
                    //oldTail.next = node 如果这个赋值操作没执行到,就会暂时出现oldTail.next = null的虚假情况,所以next节点是不可靠的,所以需要从末尾遍历,使用prev节点
                    oldTail.next = node;//老队尾节点的next指向新插入节点
                    return oldTail;
                }
            } else {
                initializeSyncQueue();
            }
        }
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值