ReentrantLock

609 阅读6分钟

ReentrantLock

构造

实现了Lock和serializable接口

ReentrantLock implements Lock, java.io.Serializable

成员变量

 private final Sync sync;
 abstract static class Sync;
 static final class NonfairSync extends Sync;
 static final class FairSync extends Sync;

提供的方法

 public void lock();
 public void lockInterruptibly();
 public boolean tryLock(); 
 public void unlock();
 public boolean tryLock(long timeout, TimeUnit unit);
 public Condition newCondition()

原理分析

//创建一个重入锁
ReentrantLock lock=new ReentrantLock();
//重入锁的构造函数可以看到,重入锁默认是非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
    }

可以看到可重入锁默认构建一个非公平锁,调用锁的lock方法,进入lock方法

        final void lock() {
            //cas设置把当前同步器的状态设置为1
            if (compareAndSetState(0, 1))
                //状态设置成功的话,当前同步器中的独占线程设置为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //如果状态没有设置成功,进入方法
                acquire(1);
        }

进入compareAndSetState方法,cas设置状态

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

进入setExclusiveOwnerThread方法,设置AQS的独占锁

    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

如果状态没有设置成功,需要进入acquire进行抢占

    public final void acquire(int arg) {
        //尝试获取锁
        if (!tryAcquire(arg) &&
            //没有获取的到,队列里面获取
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //自身中断
            selfInterrupt();
    }

进入tryAcquire方法

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

进入nonfairTryAcquire方法

 final boolean nonfairTryAcquire(int acquires) {
     		//获取当前线程
            final Thread current = Thread.currentThread();
     		//获取当前aqs状态
            int c = getState();
            if (c == 0) {
                //如果状态设置成功,说明现在还没有线程获取锁,把当前线程设置为独占锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
     		//如果当前的独占锁就是当前抢占锁的线程,aqs的当前状态+1,并且设置状态
            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;
        }

nonfairTryAcquire方法如果返回true的话说明获得了锁,返回false说明没有抢占到锁,没有抢到锁的话会进入addWaiter方法,抢到的话会返回

    private Node addWaiter(Node mode) {
        //把当前线程封装为一个node
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //把aqs中的尾节点赋值给pred
        Node pred = tail;
        //如果为节点不是空的话
        if (pred != null) {
            //把为节点赋值给当前节点的上一个节点
            node.prev = pred;
            //cas把aqs的为节点设置为当前节点
            if (compareAndSetTail(pred, node)) {
                //双向列表,把上一个节点指向的下一个节点设置为组装的node节点
                pred.next = node;
                return node;
            }
        }
        //尾节点为空的话进入方法
        enq(node);
        return node;
    }
 private Node enq(final Node node) {
        for (;;) {
            //把尾节点赋值给t
            Node t = tail;
            //当前节点是空的话
            if (t == null) { // Must initialize
                //构建一个新的节点cas把头结点设置为新节点
                if (compareAndSetHead(new Node()))
                    //把头结点赋值给tail(此时尾节点头节点都是新构建的节点)
                    tail = head;
                //再次进入循环
            } else {
                //把尾节点赋值给当前节点的前节点
                node.prev = t;
                //cas把当前节点设置为尾节点(此时的尾节点是新构建的节点)
                if (compareAndSetTail(t, node)) {
                    //把当前node节点赋值给新构建的节点的下一个节点(构建双向列表)
                    t.next = node;
                    return t;
                }
            }
        }
    }

当前线程组装node节点,并且在addWaiter构建一个双向队列,之后返回组装的节点,作为入参传递给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)) {
                    //把当前节点设置为头结点,并且把当前节点的上个节点下个节点都设置为null
                    setHead(node);
                    
                    p.next = null; // help GC
                    failed = false;
                    //返回
                    return interrupted;
                }
                //如果当前有线程获得锁,也就是当前线程没有抢到锁的话
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //挂起并检查当前线程是否被中断
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire判断在抢占锁失败后,是否该挂起

   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
       
      	//判断前节点是否是singnal状态
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
       //过滤掉canel状态的节点
        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 {
            //把前节点设置为singnal状态
            /*
             * 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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

如果前节点是singal状态,当前节点应该被挂起,parkAndCheckInterrupt方法会挂起线程

    private final boolean parkAndCheckInterrupt() {
        //挂起线程
        LockSupport.park(this);
        //复位线程状态
        return Thread.interrupted();
    }

线程到这边会挂起阻塞,等待唤醒,加入获得锁的线程释放了锁,阻塞线程被唤醒的话,parkAndCheckInterrupt返回true,在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(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //线程被唤醒继续往下走,
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

selfInterrupt()是AQS中实现,源码如下:

private static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

说明:selfInterrupt()的代码很简单,就是“当前线程”自己产生一个中断。但是,为什么需要这么做呢? 这必须结合acquireQueued()进行分析。如果在acquireQueued()中,当前线程被中断过,则执行selfInterrupt();否则不会执行。

在acquireQueued()中,即使是线程在阻塞状态被中断唤醒而获取到cpu执行权利;但是,如果该线程的前面还有其它等待锁的线程,根据公平性原则,该线程依然无法获取到锁。它会再次阻塞! 该线程再次阻塞,直到该线程被它的前面等待锁的线程锁唤醒;线程才会获取锁,然后“真正执行起来”! 也就是说,在该线程“成功获取锁并真正执行起来”之前,它的中断会被忽略并且中断标记会被清除! 因为在parkAndCheckInterrupt()中,我们线程的中断状态时调用了Thread.interrupted()。该函数不同于Thread的isInterrupted()函数,isInterrupted()仅仅返回中断状态,而interrupted()在返回当前中断状态之后,还会清除中断状态。 正因为之前的中断状态被清除了,所以这里需要调用selfInterrupt()重新产生一个中断!

小结:selfInterrupt()的作用就是当前线程自己产生一个中断。

总结

再回过头看看acquire()函数,它最终的目的是获取锁!

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

(01) 先是通过tryAcquire()尝试获取锁。获取成功的话,直接返回;尝试失败的话,再通过acquireQueued()获取锁。 (02) 尝试失败的情况下,会先通过addWaiter()来将“当前线程”加入到"CLH队列"末尾;然后调用acquireQueued(),在CLH队列中排序等待获取锁,在此过程中,线程处于休眠状态。直到获取锁了才返回。 如果在休眠等待过程中被中断过,则调用selfInterrupt()来自己产生一个中断。