ReentrantLock源码分析

ReentrantLock是Concurrent包提供的一种可重入锁。可重入锁是指当一个线程获取锁进入互斥区后可以再次拿到该锁,而不需要释放之前获取的锁。通常锁都需要设计成可重入的,否则很容易发生死锁。

ReentrantLock 是基于AQS来实现的,AQS也叫抽象同步队列器,是Concurrent包提供的一种实现同步队列的框架。AQS提供了两种模式:共享模式和排它模式,ReentrantLock基于排它模式实现的一种可重入锁。

AQS基本原理

AQS类结构如下:

 AbstractOwnableSynchronizer 是一个抽象类,只有一个成员变量exclusiveOwnerThread,表示持有锁的线程

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
    ...
   private transient Thread exclusiveOwnerThread;
    ...
}

 AbstractQueuedSynchronizer 中的成员变量很多,这些成员是实现队列同步的关键。

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
     ...
     private volatile int state;
     private transient volatile Node head; //双端队列的队头
     private transient volatile Node tail; //双端队列的队尾
     //一个节点代表一个线程
     static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        //表示当前线程取消排队
        static final int CANCELLED =  1;
        //表示当前节点的下一个节点,等待被唤醒
        static final int SIGNAL    = -1;
        //表示当前节点在等待condition,也就是在condition队列中
        static final int CONDITION = -2;
        //表示当前场景下后续的acquireShared能够得以执行; 
        static final int PROPAGATE = -3;
        //等待的状态
        volatile int waitStatus;
        //上一个节点
        volatile Node prev;
        //下一个节点
        volatile Node next;
        //当前节点代表的线程
        volatile Thread thread;
        Node nextWaiter;
        ...
     }
    //条件队列
    public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
        ...
    }

     ...
}

state:表示一种状态,当state的值为0时表示当前没有线程获取到锁,当一个线程获取到锁时,会利用cas操作将state的值从0变为1。对于可重入锁,一个线程多次获取锁,state的值会继续增长,表示重入的次数。

exclusiveOwnerThread:从AbstractOwnableSynchronizer 继承的而来,表示当前获取到锁的线程,初始时为NULL。

同步队列:没有获取到锁的线程会放到同步队列的队尾进入阻塞状态,等待被同步队列前面的元素唤醒。这个队列是由双向链表实现的双端队列。

双向链表中每一个Node节点代表一个等待的线程。节点的waitStatus表示等待的状态,初始值为0,其状态值可以为CANCELLED(1) 、SIGNA(-1)L、CONDITION(-2) 、PROPAGATE(-3)。

waitStatus的值为SIGNAL(-1)时,表示当前节点的下一个节点在等待唤醒。

waitStatus的值为CANCELLED (1)时,表示当前线程已经取消排队了。就好比你去排队购物,你发现前面排队的人太多了,干脆不排了,改天再来。

条件队列:调用Condition的awaite 方法,会使获取到锁的线程释放锁,并加入到条件等待队列中挂起。条件等待队列是一个单向链表。当其它线程调用Condition的singal方法会将条件队列中的头节点移动到同步队列的队尾,并唤醒。

ReentrantLock 类图如下:

ReentrantLock 本身并没有代码逻辑,实现都在其内部类Sync中,Sync是一个抽象类继承了AQS,它有两个子类FairSync、NonfairSync对应公平锁和非公平锁两种实现方式。

Lock()方法公平锁与非公平锁实现

非公平锁,不管队列是是否有线程在排队等锁,直接尝试获取锁。

final void lock() {
    //一上来就使用cas操作来修改state的值,也就是抢锁,不考虑同步队列中是否有其它线程在排队
    if (compareAndSetState(0, 1))
        //设置当前线程为独占锁的线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

公平锁,没有直接抢锁。

    final void lock() {
        acquire(1);
    }

 acquire 是AQS提供的一个模板方法,tryAcquire 再次尝试获取锁,被NonfairSync与FairSync分别实现。获取失败时,addWaiter 以给定模式为当前线程创建节点并将节点其放入等待排队。acquireQueued 阻塞线程,selfInterrupt 用于重置中断标识

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 nonfairTryAcquire 是 tryAcquire 的非公平锁实现:不管同步队列中是否有线程在等待获取锁,直接去尝试获取锁

        final boolean nonfairTryAcquire(int acquires) {
            //当前线程
            final Thread current = Thread.currentThread();
            //同步队列状态
            int c = getState();
            //为0表示还没有线程获取锁
            if (c == 0) {
                //获取锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //状态不为0,并且持有锁的线程为当前线程,锁重入
            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;
        }

 tryAcquire 的公平锁实现:先检查队头是否有其它线程在排队等锁

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //hasQueuedPredecessors 检查队列前面是否有线程在排队等锁,有返回true,没有返回false
                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;
        }
//主要是用来判断线程需不需要排队,因为队列是FIFO的,
//所以需要判断队列中有没有相关线程的节点已经在排队了。
//有则返回true表示线程需要排队,没有则返回false则表示线程无需排队
public final boolean hasQueuedPredecessors() {
    //读取头节点
    Node t = tail; 
   //读取尾节点
    Node h = head;
    //s是首节点h的后继节点
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

addWaiter 以给定的模式(共享或独占)为当前线程创建节点,并将节点加入到同步队列。

    private Node addWaiter(Node mode) {
        //以给定的模式创建节点
        Node node = new Node(Thread.currentThread(), mode);
        //尝试快速添加节点
        //获取同步队列的尾节点
        Node pred = tail;
        //尾节点不为空,利用cas操作将当前节点设置为尾节点,并调整指针
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //cas操作失败或者尾节点为空
        enq(node);
        return node;
    }

 enq 利用自旋锁尝试将节点加入到队列,当前头尾节点为null时,需要初始化头尾节点,将节点添加到尾部,会出现并发问题,AQS采用CAS+自旋锁的方式来处理并发问题。

同时需要关注的是,AQS同步队列的头节点是一个空节点,不代表线程。

//将node节点加入到同步队列的尾部,并返回该节点的前序节点    
private Node enq(final Node node) {
        //自旋锁
        for (;;) {
            //获取最新的尾节点
            Node t = tail;
            //尾节点为空,初始化头尾节点
            if (t == null) { // Must initialize
                //并发时,多个线程可能同时得到尾节点为空,cas操作只会有一个线程初始化成功,其他线程进入下一次循环。
                if (compareAndSetHead(new Node()))
                    tail = head;
                //完成初始化后进入下一次循环
            } else {
                //在本次循环中,头尾节点已经初始化,利用cas尝试将节点加入到尾部
                //并发时,只有一个线程操作成功,其他线程进入下一次循环,重复上述操作:获取最新的尾节点,将当前线程对应的节点添加到尾部。
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

                                                        阻塞队列示意图

acquireQueued 用于阻塞线程

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //这里使用自旋锁,是因为当线程被唤醒的时候,可以再去尝试去获取锁。
            for (;;) {
                //获取当前节点的前驱节点
                final Node p = node.predecessor();
                //如果前驱节点是头节点,就尝试获取锁
                //即使是非公平锁,入队之后也需要排队等锁
                if (p == head && tryAcquire(arg)) {
                    //获取锁成功,将头节点设为当前节点
                    //setHead不使用cas操作是因为tryAcquire返回true,表示当前线程持有锁,才会进入到此逻辑
                    setHead(node);
                    //将原来的头节点的next指针置为null,原来的头节点就失去了引用,会被GC回收
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //在获取锁失败后, 判断是否需要把当前线程挂起
                //如果需要挂起,parkAndCheckInterrupt挂起线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 shouldParkAfterFailedAcquire 方法在获取锁失败的时候,根据当前节点的前驱节点状态来判断当前线程是否需要被挂起

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前驱节点的状态,初始时节点的状态值为0
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            如果前驱节点的状态为SIGNAL,说明当前节点需要阻塞
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            //此时前驱节点的状态是CANCELLED,因此需要跳过该节点,从新设置当前节点的有效前驱节点
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            //循环找到有效前驱节点为止,CANCELLED状态的节点从队列中被移除
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            //设置前驱节点的状态为SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

前面提到了waitStatus的几种状态的含义,对于排它锁,我们只需要关心SIGNAL和CANCELLED 这两种状态。

这里再次强调:SIGNAL不是当前节点的状态,而是代表当前节点的下一个节点是否已经被挂起或者将要被挂起,从而需要被唤醒。CANCELLED 则表示当前节点已经取消排队

总结一下shouldParkAfterFailedAcquire 的主要作用:

当前驱节点的状态值是SIGNAL返回true

当前驱节点的状态值>0 也就是CANCELLED时,会从新设置当前节点的有效前驱节点,状态为CANCELLED的节点从队列中移除,并返回false

其他情况,将前驱节点的状态值,置为SIGNAL,返回false

parkAndCheckInterrupt 使用LockSupport.park 挂起线程的

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this); //线程在这里被挂起,直到被唤醒
        return Thread.interrupted(); //当线程被唤醒时,执行Thread.interrupted()判断线程是否被中断,并擦除中断标识
    }

线程调用LockSupport.park()进入阻塞状态,其他线程可以使用LockSupport.unpark(Thread t) 来唤醒该线程,同时LockSupport.park() 响应中断,也就是t.interrupt()。因此当线程被唤醒的时候,会调用Thread.interrupted()来判断线程是由于哪种情况被唤醒的

如果是被中段唤醒,由于Thread.interrupted() 会擦除中断标识,因此当线程被中断唤醒获取到锁后,会重新设置中断标识。用于后续可中断方法或者其他中断处理。

unlock() 方法源码

    public void unlock() {
        sync.release(1);
    }
    
    public final boolean release(int arg) {
        //尝试释放锁
        if (tryRelease(arg)) {
            //锁释放完成后唤醒队列中的下一个节点
            //从lock方法中我们可以知道,线程获取到锁后,代表该线程的节点是head
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

     protected final boolean tryRelease(int releases) {
            //减少重入次数
            int c = getState() - releases;
            //如果当前节点不是持有锁的线程,抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //重入次数为0,释放锁,将持有锁的线程置为null
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //重新设置状态值,这里没有使用cas,
            //调用unlock方法前,必须调用lock()方法获取锁,同时对于独占锁,锁由线程独占,不存在竞争,所以不需要使用cas来设置状态
            setState(c);
            return free;
     }

lockInterruptibly() 源码分析

该方法会响应中断,抛出中断异常,与lock()的区别主要体现在doAcquireInterruptibly 中,收到中断信号之后,直接抛出异常,并返回

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        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);
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值