ReentrantLock重入锁

  上次博客讲到了经过wait()方法和notify()方法来实现循环打印数字和字母得问题。其实使用重入锁也能够实现一样得功能,那么开始咱们先经过源码来了解一下重入锁把。java

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

首先它有一个lock()方法,它用来加锁,从代码中能够看到,它调用得是sync.lock()方法,node

 
 
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** 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();
 

在这个类里面,有一个静态抽象类Sync对象以及Sync得属性,所以咱们能够知道它调用得是Sync里面得lock()方法,而Sync又是一个抽象类,lock()方法也是一个抽象方法,具体由它得子类去实现。安全

而后咱们接着看ReentrantLock得构造方法ui

    /**
     * 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();
    }

经过代码能够看到,在初始化得时候会初始化Sync对象,经过代码能够看出若是不带参数得话默认使用得是NonfairSync这个子类,也能够指定使用FairSync这个子类。好了,经过以上代码咱们能够知道,ReentrantLock这个类会在实例化得时候指定FairSync或者NonFairSync,下面咱们来介绍一下这两个类。首先经过字面意思能够看出前者得意思是“公平锁”,后者得意思是“非公平锁”。那么为何要这么叫呢,实际上是由于他们得lock()实现方法得差别。下面咱们就来一一介绍,首先介绍FairSyncthis

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

从代码能够看出最后得实现是acquire(1)这个方法,那么这个方法干了什么呢?看代码其实挺少得,其实干得事情并很多。首先会执行tryAcquire(arg)这个方法。一样,看代码。spa

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;
        }

咱们一步步来看,首先获取当前得线程,而后或者状态c,这个c很重要,它得含义就是这个“重入”得含义,等会再说。getState()方法经过代码能够看出返回得是ReentrantLock里面得state属性,由于是int类型,因此默认为0,表明没有线程正在使用它,这里讲了c这个变量得含义,咱们接着往下看,若是c等于0,也就是说没有线程正在使用,那么他会进入下一个if判断,首先会执行hasQueuedPredecessors()方法。一样继续点进去看代码实现线程

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());
    }

这个方法是AbstractQueuedSynchronizer这个类里面得方法,经过代码能够看出这个类里面维护了一个FIFO队列,队列中每个元素都是一个Node节点,其中Node对象有prev属性用来表示前一个节点,next属性用来表示下一个节点,thread属性用来标识当前线程,队列有一个head(头)节点和tail(尾)节点,head节点仅保存下一个节点得引用。就简单介绍这么点,由于这些是用来帮助咱们理解上面代码得含义得。下面我用中文描述一下这个判断干了什么事情,返回 头节点 != 尾节点而且(头节点得下一个节点为空或者头节点得下一个节点得线程不等于当前线程),这么说有一点绕,其实咱们看方法名能够知道这个方法是用来判断是否存在等待着得对象想要得到锁,首先假设队列为空,那么头节点等于尾节点,返回false,若是头节点不等于尾节点,那么头节点得下一个节点确定不为空,而后判断头节点得下一个节点得线程是否是当前线程,若是是,返回false,若是不是,返回true。code

而后咱们再回归tryAcquire()方法。若是没有等着着得线程。那么它会执行compareAndSetState()方法。这个方法得底层是经过CAS来实现得,这里简单得介绍一下CAS,CAS是一种使用无锁得方式来实现线程安全得方法,这个方法有三个参数,一个是要更新得遍历V,一个是预期值E,一个是新值N,若是V == E,那么更新V为N,若是V != E,那么证实有其余线程更改了这个变量,这个方法不会作任何事情,你能够再从新执行这个方法或者选择放弃。主要流程就是这个,有兴趣得能够去了解一下CAS。若是操做成功,设置当前线程为正在使用得线程,返回true。这里讲解得是c等于0得状况,若是c不等于0呢?判断得到锁得线程是否是当前线程,若是是,c加1,看到这里其实也就明白了重入锁这个词是怎么来的了,它能够一直调用lock方法来加锁,每调用一次,state加1。orm

而后再回归acquire()方法。若是tryAcquire()方法获取锁成功,那么不会执行其余操做,若是失败,会执行acquireQueued(addWaiter(Node.EXCLUSIVE), args)这个方法,而且当前线程阻塞。那么这个方法又干了什么呢?先来看addWaiter(Node.EXCLUSIVE)这个方法,对象

static final Node EXCLUSIVE = null;
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;
    }

这个方法判断队列是否为空,若是为空,执行enq()方法,不为空,执行上面得方法,最终得结果都是将当前线程假如到等待队列中。而后acquireQueued()这个方法一层一层得,我没有看太懂,这里也就不说太多,功能其实就是让等待队列前面得获取锁。好了,整个FairSync的lock()方法已经介绍完了,那么NonFairSync的lock()方法有什么区别呢?

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

从代码中能够看到,它不会去管等待队列什么的,而是直接执行CAS操做,若是失败了,好吧,失败了大不了我就用FairSync的那一套咯。

而后咱们再来看看unlock()方法。

public void unlock() {
        sync.release(1);
    }
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

从代码能够看出,会执行tryRelease()方法,若是成功,而且等待队列不为空的话,唤醒队列中第一个等待的线程。

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;
        }

由于重入锁能够屡次加锁,所以只有当c为0时,才能返回true。不然,返回false。也就是说,lock几回就要unlock几回才能释放锁。

整个ReentrantLock主要的就介绍完了,这些东西是经过查看源码以及其余的博客整理出来的,整个代码的讲解也是我本身的理解,可能语言组织方面不是太好。但愿本身的讲述不是太差劲。。。这篇主要整理了ReetrantLock的原理,下篇博客我准备使用ReentrantLock来实现循环打印数字及字母。

相关文章
相关标签/搜索