ReentrantLock源码分析


title: JUC.ReentrantLock

ReentrantLock提纲

ReentrantLock简介

ReentrantLock是可重入的独占锁,同时只允许一个线程获取该锁,其它被阻塞的锁会放入AQS队列里面。首先看下ReentrantLock的类图

avatar
从类图可以看出来ReentrantLock使用AQS实现,并且内部实现了公平锁与非公平锁,默认是非公平锁。
其中Sync继承AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平和公平策略。
公平锁是指按照队列排队唤醒线程,非公平锁是指当释放锁有队列外的线程参与抢占可以不用参与队列。
所以一般来说非公平锁性能高于公平锁,因为不用唤醒线程。公平锁除非有特许业务场景需要控制线程调用情况有可能会用到。(反正我至今没遇到过)
接着往下说ReentrantLock,由于基于AQS实现所以也会用到state状态值表示锁情况。这里的state表示重入次数,默认情况下是0标识当前锁没有被任何线程持有。
当线程尝试获取锁的时候会使用CAS修改设置state的值为1如果成功则获取锁,然后记录锁的持有者是该线程,其余则放入AQS队列当中。
在该持锁线程第二次获取锁的时候state修改为2,这就是重入次数,每释放一次锁就减1当为0的时候就彻底释放了锁。

获取与释放锁

在说到获取与释放锁的时候我会结合源代码来说,比较简洁明了。
首先看下面一段模仿ArrayBlockingQueue的代码

/**
 * @ClassName ConsumerAndProducer
 * @Description ToDo
 * @Author Allen
 * @Date 2018/12/4 21:00
 * @Version
 */
public class ConsumerAndProducer {

    private final static ReentrantLock reentrantLock = new ReentrantLock();
    private final static Condition productCondition = reentrantLock.newCondition();
    private final static Condition consumeCondition = reentrantLock.newCondition();
    private final static Queue <String> queue = new LinkedBlockingQueue <String>();
    private final static int QUEUE_SIZE = 10;


    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            reentrantLock.lock();
            try {
                //队列满了等待
                while (queue.size() == QUEUE_SIZE) {
                    productCondition.await();
                }
                //添加元素
                queue.add("lock");
                //唤醒消费
                consumeCondition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放锁
                reentrantLock.unlock();
            }
        });

        Thread consumer = new Thread(() -> {
            reentrantLock.lock();
            try {
                while (0 == queue.size()) {
                    consumeCondition.await();
                }
                System.out.println(queue.poll());
                productCondition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        });

        producer.start();
        consumer.start();

    }
}
  • 获取锁
    看上面的代码首先会调用lock方法获取锁,咱们看一眼源码发现委托给了Sync的lock方法,会根据ReentrantLock构造函数选择Sync的实现是NonfairSync还是FairSync
    分别看下非公平和公平的实现,首先看非公平的方式如下代码
      final void lock() {
            //上来如果锁没被占用则直接占用简直不讲道理,如果锁被占用则走流程该进队列进队列
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

然后咱们再看一眼非公平锁的tryAcquire实现,这个代码也是挺暴躁的。真的丧心病狂的要抢占锁。

      protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

      final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //state为0直接占锁
            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;
            }
            //都不是走流程进AQS队列
            return false;
        }  

非公平锁总则来说就是怎么拿锁块怎么来。下面说一下公平锁,先看代码

        final void lock() {
        //很文明上来走流程
            acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //锁没被占用先走一个队列判断hasQueuedPredecessors
            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;
        }
        //下面的h!=t队列不为空,然后如果且next不为空更厉害的是还等于当前线程则拿锁
        //h!=t且next不为空然后不等于当前线程,则进队列取next的线程出来。相当的公平呀。。。。
        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());
    }

  • 获取锁
    释放锁如下面的代码其实没啥好看的就是state减少1,并且如果sate为0了释放锁的占有,当前锁的线程为null.
   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;
        }

Condition讲解

其实在写AQS的时候有提到过这个东西,但是没有举例子详细说明。还是回到本文的第一段代码ConsumerAndProducer
这段代码是模范的ArrayBlockingQueue的代码,其中会有两个condition。每一个condition都是一个队列可以存放调用线程并且唤醒其中的线程。
这里的好处是啥了?比如consumeCondition里面有线程A然后productCondition里面有线程B和D。我唤醒的时候可以只唤醒A,而不会唤醒所有的线程。
而不会存在所有的线程都唤醒的尴尬局面,比如读和写咱们的线程可以灵活分开来管理是不是很cool。

欢迎扫码加入知识星球继续讨论
avatar

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值