并发编程6-ReentrantLock

1.什么是可重入锁ReentrantLock?

就是支持重新进入的锁,表示该锁能够支持一个线程对资源的重复加锁。底层实现原理主要是利用通过继承AQS来实现的,也是利用通过对volatile state的CAS操作+CLH队列来实现;

ReentrantLock支持公平锁和非公平锁。

CAS:Compare and Swap 比较并交换。CAS的思想很简单:3个参数,一个当前内存值V、预期值A,即将更新的值B,当前仅当预期值A和内存值V相等的时候,将内存值V修改为B,否则什么都不做。该操作是一个原子操作被广泛的用于java的底层实现中,在java中,CAS主要是有sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现;

CLH队列:也叫同步队列,是带头结点的双向非循环列表,是AQS的主要实现原理(结构如下图所示)
在这里插入图片描述

2.ReentrantLock分为公平锁和非公平锁:区别是在于获取锁的机制上是否公平。

(1)公平锁:公平的获取锁,也就是等待时间最长的线程最优获取到锁,ReentraantLock是基于同步队列AQS来管理获取锁的线程。 在公平的机制下,线程依次排队获取锁,先进入队列排队的线程,等到时间越长的线程最优获取到锁。
(2)非公平锁:而在“非公平”的机制下,在锁是可获取状态时,不管自己是否在对头都会获取锁。
(3) 公平锁和非公平锁的对比
1、公平锁用来解决“线程饥饿”的问题,即先进入CLH同步队列等待的线程,即同步队列中的头结点总是先获取到锁。而非公平锁会出现 一个线程连续多次获取锁的情况,使得其他线程只能在同步队列中等待。
2、经过测试,10个线程,每一个线程获取100 000次锁,通过vmstat统计运行时系统线程上下文切换的次数;公平锁总耗时为:5754ms,而费公平锁总耗时为61ms。总耗时:公平锁/非公平锁=94.3倍,总切换次数 :公平锁/非公平锁=133倍。非 公平锁的线程切换更少,保证了更大的吞吐量。
(4)可重入锁的结构如下:
在这里插入图片描述

3.非公平锁获取的实现源码如下:按照代码的执行顺序

先看使用例子

public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
}

然后看源代码是怎么执行的
先初始化

  public ReentrantLock() {
        sync = new NonfairSync();
    }

然后调用内部类NonfairSync的lock方法

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

(1)调用lock方法

/**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 1.Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.调用lock方法
         */
        final void lock() {
            //判断当前state是否为0,即没有被任何线程获取的状态,如果是,CAS更新为1,当前线程获取到了非公平锁
            if (compareAndSetState(0, 1))
                //设置当前锁的持有者线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
            //如果状态不是0,返回false走acquire(1)方法 
                acquire(1); //尝试获取锁,若获取不到,加入等待队列自旋,直至获取到锁
        }
        
     protected final boolean tryAcquire(int acquires) {   return nonfairTryAcquire(acquires);  }//重写了aqs的tryAcquire方法
    }

2)如果状态不是0,返回false,走acquire(1)方法 :

在讲aqs的时候说过这个方法,主要作用是:尝试获取锁,若获取不到,加入等待队列自旋,直至获取到锁。aqs的封装让ReentrantLock实现代码更简洁。具体可以参照 《并发编程4-AQS同步器原理

/**2 获取非公平锁
     * 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) {
   // tryAcquire判断是否获取到锁
   // addWaiter(Node.EXCLUSIVE) 加入等待队列
   // acquireQueued循环获取同步状态
        if (!tryAcquire(arg) &&   //下面主要讲下这个方法
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//这两个方法,在这里就不讲了,参考我的另外一篇AQS文章
            selfInterrupt();
    }

(3).调用静态内部类NonfairSync重写AQS的tryAcquire(1)方法:

   //3.调用静态内部类NonfairSync重写AQS的tryAcquire方法
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }

(4) 走nonfairTryAcquire(1)非公平锁的实现方法:重点分析下这个方法

/**
         * 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();
            //c == 0 说明锁没有被任何线程所拥有,则CAS设置锁的状态为acquires
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                   //设置当前线程为锁的拥有者
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果锁的持有者已经是当前线程,更新锁的状态,这个地方就是为什么可重入的原因,如果获取锁的线程再次请求,则将同步状态的值增加1,并返回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;
        }

4.获取公平锁的过程

(1)公平锁源码获取源码如下:

/**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        //直接调用acquire(1)方法
        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) {
               //c==0表示锁没有被任何线程锁拥有,首先判断当前线程是否为CLH同步队列的第一个线程;是的话,获取该锁,设置锁的状态
                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;
        }
    }

从源码可以看出,和nonfairTryAcquire(int acquires)比较唯一不同的是 ,判断条件多了hasQueuePredecessors()方法,加入了当前节点是否有前驱节点的判断。如果返回true,表示有线程比当前线程等待的时间长,需要等待前驱线程获取并释放锁之后才能获取锁。返回false,表示当前的线程所在的节点为队列(CLH队列)的头节点,可以直接获取锁。

    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;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

5.释放锁:

(1)首先调用ReetrantLock重写父类AQS的unlock方法:

/**
     * Attempts to release this lock.
     *
     * <p>If the current thread is the holder of this lock then the hold
     * count is decremented.  If the hold count is now zero then the lock
     * is released.  If the current thread is not the holder of this
     * lock then {@link } is thrown.
     *
     * @throws IllegalMonitorStateException if the current thread does not
     *         hold this lock
     */
    public void unlock() {
        sync.release(1);
    }

(2)调用AQS里面实现的释放互斥锁的方法:首先进入tryRelease()方法来尝试释放当前线程持有的锁,如果成功的话,调用unparkSuccessor唤醒后继线程。

/**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

(3)进入tryRelease方法我们重点分析:重写父类AQS里面的模板方法,进行锁的释放:

protected final boolean tryRelease(int releases) {
       //c是本次释放锁之后的状态
            int c = getState() - releases;
           // 如果当前线程不是锁的持有者线程,则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //c==0表示锁被当前线程已经彻底释放,则将占有同步状态的线程设置为Null,即锁变为可获取的状态,这个时候才能返回true,否则返回false
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //否则设置同步状态
            setState(c);
            return free;
        }

(4)释放锁成功后,即锁变为可获取的状态后,调用unparkSuccessor唤醒后继线程,进入unparkSuccessor的源码:

/**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
      获取当前节点的有效的后继节点,这的有效是指后继节点s不为null并且waitStatus是<=0的,
          既没有被取消的状态。无效的话,通过for循环遍历,一直找到一个有效的节点
         */
         
        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;
        }
     //唤醒有效后继节点对应的线程
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值