AQS: ReentrantLock之加锁

多线程开发的场景下我们不可避免的会用到锁,那么java中也有各种锁,我们应该怎么使用呢?以及它的原理?

首先synchronized关键字相信大家是不会陌生的,这个关键字要理解其原理的话就涉及到了jvm了,并且jdk现在也对它做了很大的优化,比如: 偏向锁,轻量锁,重量锁。。膨胀,撤销等等,这篇文章目前就不过多作介绍。

其实如果我们仔细想想,我们为什么要使用ReentrantLock,直接使用cas不是就行吗?

cas不是在多线程的情况下也可以用吗?1.8版本之前的ConcurrentHashMap之前不就是这样做的吗?

原因就是cas会发生自旋,cpu使率会急剧升高(cpu不停的在各线程间切换),严重影响系统性能,所以

java.util.concurrent.locks.ReentrantLock派上用场了

看源码前我们先把它的优势写出来,并且我们后面通过源码去验证查看,

AQS:AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件。

ReentranLock是通过(AQS中)链表(多个Node连接)来管理我们对同一资源抢占的线程,并且当其它线程在获取锁的时候并不是所有线程都在竞争锁,而是除了头节点外的第二个线程在竞争锁,其它线程都处于休眠状态(我们暂且这么认为),当第二个线程获得锁后第三个线程就进入竞争获取锁得状态了。所以每时每刻都只有一个线程在竞争锁,减少了cpu的不停的在各线程间的切换

下面我们就分析下ReentrantLock加锁的源码,你就可以知道答案了。

ReentrantLock  实现了Lock接口,lock接口也可以说是jul包下一个牛逼哄哄的接口,下面列举一些他的一些方法:

//上锁: 这个会阻塞线程: 会一直去尝试获取锁,直到获得了锁
void lock();
//尝试去获取锁并且返回是否获取到锁,不会造成阻塞
boolean tryLock();
//尝试获取锁,如果在规定时间内获取不到就放弃
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

目前先介绍列这三种,具体的可以看看源码

首先我们获取锁后,必须要释放锁,否则锁一直不会释放,那就完蛋了。

lock.lock();
try{
    System.out.println(Thread.currentThread().getName()+"获得了锁");
    while(true){

    }
}catch (Exception e){

}finally {
    //释放锁
    System.out.println(Thread.currentThread().getName()+"释放锁");
    lock.unlock();
}

言归正传,要把ReentrantLock原理理解透,那就要把它的数据结构以及内部方法,实现搞懂,这样就知道如何去用,也就更容易理解它的原理了。

ReentrantLock实现锁功能是通过其内部的一个抽象静态内部类:

/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
 * Base of synchronization control for this lock. Subclassed
 * into fair and nonfair versions below. Uses AQS state to
 * represent the number of holds on the lock.
 */
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock();

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
     */
    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中又包含了公平锁(FairSync)和非公平锁(NonFairSync)

后面会讲到到底什么是公平和非公平

这两者都继承了Sync这个类,而Sync又继承了AbstractQueuedSynchronizer(AQS)

最中还是通过AQS来实现我们的锁,所以这才是最终的大BOSS

下面画一下这些类的大概关系:

通过源码可见我们所有的操作基本上都是和这个Node链表有关系,

Node 主要包含: pre 上一个节点, next 下一个节点,tail 尾节点,head 头结点,waitStatus  等待状态,thread 线程对象

AQS默认会创建一个空的Node如下图:

这也是Node的一个大体结构,下面画一个多个node的图

下面我们就一步一步的去探究究竟是怎么回事;

1 首先从创建一个ReentrantLock说起:

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     * 创建对象,从构造方法中可以看到默认创建的的是非公平锁
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     *  创建对象是可以指定创建锁的类型
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

   这也就是上面说的他们之前的关系;其实觉得ReentranLock就是一个空壳子,内部的一些操作都是依赖AQS,Sync及它的子类(FairSync和NonfairSync)实现

2 调用lock();方法:

以下为伪代码

//创建一个公平锁    
1 public static Lock lock=new ReentrantLock(true);
//加锁
2 lock.lock();
2.1  去获取锁
/**
     * Acquires the lock.
     *
     * <p>Acquires the lock if it is not held by another thread and returns
     * immediately, setting the lock hold count to one.
     *
     * <p>If the current thread already holds the lock then the hold
     * count is incremented by one and the method returns immediately.
     *
     * <p>If the lock is held by another thread then the
     * current thread becomes disabled for thread scheduling
     * purposes and lies dormant until the lock has been acquired,
     * at which time the lock hold count is set to one.
     */
    public void lock() {
        sync.lock();
    }

下面就sync.lock();这个方法做详细的解答,因为这也就是这本篇文章的核心

因为我创建的是一个公平锁,所以会调用FairSync中的加锁方法finl void lock();

下面试公平锁的代码:

//静态内部类: 公平锁    
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                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;
        }
    }

在lock的方法中调用了AQS类中的方法:

这个类中AbstractQueuedSynchronizer (AQS)
/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     * 这个方法才是真正去调用加锁的方法
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    

1 tryAcquire(args)方法是去判断用户是否取得到了锁,下面我们具体看看这个方法的代码

//注意这个方法其实是上面FairSync类中的
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获得当前锁的一个状态  为0表示没有线程持有锁,是自由状态,可以去竞争
            int c = getState();
            if (c == 0) {
                    //判断队列中是否有线程在等待,
                    //如果没有线程在等待那么就可以通过cas去获得锁
                //下面解释这个方法的源码
                if (!hasQueuedPredecessors() &&
                    //通过cas去获得锁
                    compareAndSetState(0, acquires)) {
                    //获得锁成功后设置当前持有锁的线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断当前线程是不是目前持有所得线程,如果是c(status)加1
            // 表示可重入锁,再次获得锁,直接返回true
            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;
        }

下面介绍下hasQueuedPredcessors()方法的具体实现

    /**
     * Queries whether any threads have been waiting to acquire longer
     * than the current thread.
     *
     * <p>An invocation of this method is equivalent to (but may be
     * more efficient than):
     *  <pre> {@code
     * getFirstQueuedThread() != Thread.currentThread() &&
     * hasQueuedThreads()}</pre>
     *
     * <p>Note that because cancellations due to interrupts and
     * timeouts may occur at any time, a {@code true} return does not
     * guarantee that some other thread will acquire before the current
     * thread.  Likewise, it is possible for another thread to win a
     * race to enqueue after this method has returned {@code false},
     * due to the queue being empty.
     *
     * <p>This method is designed to be used by a fair synchronizer to
     * avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.
     * Such a synchronizer's {@link #tryAcquire} method should return
     * {@code false}, and its {@link #tryAcquireShared} method should
     * return a negative value, if this method returns {@code true}
     * (unless this is a reentrant acquire).  For example, the {@code
     * tryAcquire} method for a fair, reentrant, exclusive mode
     * synchronizer might look like this:
     *
     *  <pre> {@code
     * protected boolean tryAcquire(int arg) {
     *   if (isHeldExclusively()) {
     *     // A reentrant acquire; increment hold count
     *     return true;
     *   } else if (hasQueuedPredecessors()) {
     *     return false;
     *   } else {
     *     // try to acquire normally
     *   }
     * }}</pre>
     *
     * @return {@code true} if there is a queued thread preceding the
     *         current thread, and {@code false} if the current thread
     *         is at the head of the queue or the queue is empty
     * @since 1.7
     * 总结来讲就是判断当前队列中是否有线程在排队(等待锁的状态),如果有则返回true
     *  返回false表示当前线程有资格去获取锁,为true表示当前线程无资格获取锁
     */
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        //尾节点
        Node t = tail; // Read fields in reverse initialization order
        //头节点
        Node h = head;
        Node s;
        //h != t 表示头结点和尾节点不是同一个Node  如果条件为真表示队列中有在等待的线程
        //否则表示队列目前无在等待的线程,返回false 
        return h != t &&
            //下一个为空||当前节点的线程不等于头结点下一个节点的线程(作用是判断两个线程是否相同-                
             -可重入)
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

通过上面的源码解析tryAcquire()方法所做的是也就明了: 在此总结下这个方法的作用

当一个相乘尝试去获取锁时,首先判断当前锁是不是自由态(即无线程持有),如果是自由态那么就去判断AQS队列(由Node节点形成的一个链表)中是否有线程在等待获取锁,如果有当前相线程就没有资格获取锁;

其中如果所不是自由态(被线程持有),会判断当前线程是不是持有这把锁的线程,如果是直接获得锁------可重入锁

h != t && ((s = h.next) == null || s.thread != Thread.currentThread());

这段比较好奇 为什么说(s = h.next) == null 表示队列中有在排队呢?以及是怎么出现这种状况的
看前面:h != t 出现的情况

 当前队列中说明有线程在排队

    h有两种状态: 不为空和为空

     h和t组合三种情况
1 两者都为空,排除这种状态

2 两者都不为空 目前场景也排出这种情况,因为如果是这样 s = h.next) == null 就不可能成立

3 一者为空一者不为空 出现这种情况 就是因为当前线程前一个线程在初始化队列并且 只给h = new Node(), 没有对t进行赋值,这样就会出现 h!=t && s = h.next) == null 的情况,也就是在addWriter(Node.EXCLUSIVE) 初始化节点这个方法中

tryAcquire(args) 返回true表示当前线程已经获得了锁, 返回false表示没获得锁,没获得锁的话就需要直线下面的方法了

2  acquireQueued(addWriter(Node.EXCLUSIVE),args);

  addWriter(Node.EXCLUSIVE) 初始化节点,并加入队列中


    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //将节点加入队列,也会进行队列初始化
        enq(node);
        return node;
    }

接下来就是重头戏了: 当前节点根据上一个节点判断有无竞争资格,话不多说直接上代码:

 /*
     * Various flavors of acquire, varying in exclusive/shared and
     * control modes.  Each is mostly the same, but annoyingly
     * different.  Only a little bit of factoring is possible due to
     * interactions of exception mechanics (including ensuring that we
     * cancel if tryAcquire throws exception) and other control, at
     * least not without hurting performance too much.
     */

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     * 使第二个节点处于竞争状态,其余节点处于等待状态: waitStatus在此处作用很大
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获得当前节点的上一个节点
                final Node p = node.predecessor();
                //p==head成立的话表示当前节点是第二个节点然后可以去竞争锁,
                //否则的话通过下面的方法将当前节点的上一个节点的状态更改
                if (p == head && tryAcquire(arg)) {
                    //竞争到锁了,将当前节点删除
                    setHead(node);
                    //将当前节点的引用去掉,让GC回收
                    p.next = null; // help GC
                    failed = false;
                    //结束
                    return interrupted;
                }
                //这个方法主要是获取当前节点上一个节点的状态,并且将它值为-1(waitStatus)待竞争                            
                 状态
                //当前节点为0 (可理解为沉睡状态),其实两者都可理解为沉睡状态
                //-1 类似于就绪,0表示刚创建,
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

解读下shouldParkAfterFailedAcquire(Node pre,Node node)方法:

这个方法主要是获取当前节点上一个节点的状态,并且将它值为-1(waitStatus)待竞争                            
                 状态

/**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     * 更改当前节点上一个节点的状态,以及将已经取消的线程移除队列
     * 
     /
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //初始值为0 
        int ws = pred.waitStatus;
        //-1 表示处于待竞争状态
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            //线程已经取消,并且将它移除
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            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.
             */
             //将上一个节点的waitStatus更新为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

到这里ReentranLock加锁的原理分析也就结束了,从上面也可以看出AQS的原理。

以上是一公平锁加锁来分析的,其实非公平锁和公平锁大同小异,

公平和非公平主要区别在于公平锁加锁时如果锁处于自由态,公平锁并不能直接去竞争锁,而是应该去判断队列中是否有在等待或者说处于竞争锁状态的线程,如果有那么当前线程就加入队列并且处于等待状态,

但是非公平锁就不一样,线程一开始就通过cas去获取锁(无论锁是否是自由态),获取不到的话就判断当前锁是否是自由态,如果是再次通过cas去获取锁,如果还获取不到就加入等待队列中。

非公平锁有两次竞争锁的机会,如果没竞争到就和公平锁一样了,等待

所以不管是公平锁还是非公平锁都是: 一朝排队,永久排队。

好了aqs加锁过程接这样结束了,有不对的地方欢迎指正!!!

后面更新aqs解锁

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坑里水库

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值