ReentrantLock的lock方法

245 阅读4分钟

1.程序调用lock()方法来进入lock的流程

public class TestLock {

    private int count = 0;

    public static void main(String[] args) {
        TestLock t = new TestLock();
        Lock lock = new ReentrantLock();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    lock.lock();
                    t.count++;
                    System.out.println(t.count);
                    lock.unlock();
                }
            }).start();
        }


    }
}

先设置一个简单的上锁方法运行看看结果:

发现能够正确输出,说明上锁有效。

2.debug方式解析源码

先总体概括一下:

  • 调用ReentrantLock类的lock()方法

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

发现lock()方法内部调用的了sync.lock(),这个sync为ReentrantLock的内部类

  • Sync的lock()方法:

    final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }

​ 这个lock方法是Sync的一个具体的方法,为ReentrantLock另一个内部类NonfairSync的一个方法,这个方法就开始真正的开始做上锁的操作了

我们一句一句的看这个代码的:

  • compareAndSetState(0, 1) ,这个代表用CAS的操作去设置一下AQS的内部的一个state属性,如果成功,则就执行setExclusiveOwnerThread(Thread.currentThread()) 这个是将当前线程设置为独占,也就是相当于获取到了当前这把锁
  • acquire(1)这个是调用了AQS内部的方法

AQS实现acquire()

acquire()具体代码如下:

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&//tryAcquire是尝试去获取锁,如果获取到了就返回,如果获取失败了则调用线程中断的方法来中断当前线程
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//这句代码分成了两步,先执行addWaiter()方法来将当前线程放置到AQS的一个等待队列的末尾,acquireQueueed()方法是将等待线程的头结点去获取锁,如果获取到了锁,则将当前的线程放到队列的头结点中,去等待获取锁
            selfInterrupt();
    }
  • tryAcquire(arg) 掉用的是Sync的nonfairTryAcquire方法

    final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState();//获取到当前state的值 if (c == 0) { // 如果state的值为0,则代表当前的锁没有线程占用,则进去用CAS的方式直接占用锁,并返回true,表示当前线程占用到了这把锁 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { //如果state不是等于0,则代表当前锁已经被占用了,需要判断占用的锁的线程是不是当前线程,如果是当前线程的话则直接进入,并将state的值加一,代表重入了一次,这也是重入锁的表现 int nextc = c + acquires; //state的值加一 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc);//设置state的值 return true; } return false; }

注:state是AQS的核心,这个变量是voaltile修饰的,代表锁的重入次数

  • addWaiter() 方法 由于AQS的内部有一个等待队列,队列的每个节点就是Node对象

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode); //将当前线程封装成Node对象
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) { //如果队列不为空的话就用CAS的方式设置新建的Node对象为尾结点,
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);//如果队列为空,则将Node节点插入到队列中
        return node;
    }
    

注:Node也是AQS重要的组成部分

  • acquireQueued() 这个方法的作用就是调用队列中的节点去获取锁

    final boolean acquireQueued(final Node node, int arg) { //这个node为我们刚刚新建的当前线程的节点,是队列中的尾结点
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); //for循环的方式来走到队列的头结点来
                if (p == head && tryAcquire(arg)) { //再用CAS去获取一遍锁
                    setHead(node);//当前这个节点设置为头结点,更好的争抢锁,所以为非公平锁
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) //获取锁失败之后要将该线程设置为中断的状态,避免浪费CPU的资源
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    

总结

ReentrantLocklock方法的内部源码实现是使用了AQS的实现,而我们也通过这一遍的debug的方式看到了AQS的内部频繁的使用了CAS这个自旋的操作,由于是无锁,所以效率非常的高。我们也知道ReentrantLock是一个可重入锁,具体我们也看到了其的内部实现,就是使用了AQS的state这个属性,只要是当前获取到锁的线程再重新进来的时候,我们的state值就加一,代表重入的次数增加了一次,由于state是volatile修饰的属性,所以他可以保证在多线程下的可见性。

​ 我们还看到了AQS中一个非常重要的属性 NodeAQS内部获取锁的时候有一个等待队列,队列中的每个节点都是Node对象,所以我们可以知道AQS 的核心就是voaltile修饰的statenode对象