ReentrantLock源码分析

AQS简要

reentrantLock内部最重要的实现是基于这个同步容器做的

官方解释

AQS(AbstractQueuedSynchronizer):为java中管理锁的抽象类。该类为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同

步器(信号量、事件,等等)提供一个框架。该类提供了一个非常重要的机制,在JDK API中是这样描述的:为实现依赖于先进先出

(FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状

态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假

定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使

用 getState()、setState(int) 和 compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。

这么长的话用一句话概括就是:维护锁的当前状态和线程等待列表。

 

 

AQS同步队列结构

 

ReentrantLock源码分析_第1张图片

 

获取同步状态的流程图

 

ReentrantLock源码分析_第2张图片

 

 

 

 

 

reentrantLock结构图

ReentrantLock源码分析_第3张图片

 

reentrantLock默认是非公平锁,此处先讲了公平锁

公平锁(FairSync)

lock()

当调用ReentrantLock的lock方法时,会直接调用他的内部对象Sync的方法

public void lock() {
    sync.lock();
}
final void lock() {
    acquire(1);
}

由上可知,最终会调用AQS的acquire方法

 

public final void acquire(int arg) {
    // 尝试获取锁,如果获取锁失败,那么将当前线程执行信息,放入到AQS的内部队列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 中断当前线程
        selfInterrupt();
}

tryAcquire方法是在FairySync中实现的,具体源码如下

protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取变量state, 这个变量非常重要,全程主要是围绕这个变量来做事
    int c = getState();
    // 当变量state==0 的时候,表示当前锁是空闲的,可以获取
    if (c == 0) { 
        // 判断当前线程是不是等待最久的线程,就是说判断当前线程是不是在等待队列的第二个元素
        // 因为队列head是当前(之前)拥有锁的线程
        // 如果是,则表示等待时间最久 , 返回false , 表示没有线程比它等的更久
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 设置当前执行线程(一个内部变量,为了后面的可重入功能)为 “当前线程”
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // state !=0 , 并且当前线程==当前执行器线程(一个内部变量)
    else if (current == getExclusiveOwnerThread()) {
        // 对state赋值, 由此可以看出,ReentrantLock是一个可重入锁,
       // 但是有一点,就是重入多少次,就必须要unlock多少次,以保证最终state==0 
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

公平锁的格外处理,保证等待最久的线程被优先获取锁

public final boolean hasQueuedPredecessors() {
    Node t = tail; // 尾部元素
    Node h = head; // 头部元素
    // 队列为空的情况下也是返回false
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
addWaiter方法在AbstractQueuedSynchronizer中实现
private Node addWaiter(Node mode) {
    // 构建一个节点
    Node node = new Node(Thread.currentThread(), mode);
    // 放在队列尾端,设置当前的尾节点,为新建节点的上级节点
    Node pred = tail;
    if (pred != null) {
        node.prev = pred; //如果尾部节点存在,那么新加入的节点的上级节点为之前的尾部节点
        if (compareAndSetTail(pred, node)) { //通过CAS操作设置尾部节点
            pred.next = node; //之前的尾部节点,把他的下级节点设置为新加入的那个
            return node;
        }
    }
 
    enq(node);
    return node;
}
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // 说明队列为null,必须初始化
				//compareAndSetHead 初始化一个空的头部节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
}

以上代码,通过compareAndSetTail(Node expect,Node update)方法来确保节点能够被线程安全的添加到同步队列的尾部。

在enq(final Node node)方法中,同步器通过“死循环”来保证节点的正确添加,在死循环中,只有通过CAS将节点设置为尾

节点后,当前线程才能从该方法返回,否则,当前线程不断得尝试设置

然后还有一个方法,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;
                }
                // 此处应该是检查上级节点的waitStatue , 判断是否要阻塞当前节点线程
                // parkAndCheckInterrupt() 就是调用线程阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
  }

 

 

通过上面可以得知,锁的竞争主要是获取state的状态,0表示没有线程占有他,大于0表示已经被占用了。 

公平锁的实现在于判断当前线程是否是head的下一个元素,如果是,那么就OK,就是你了。   因为head一般是

上一个占有锁的线程所有。具体获取同步状态的流程图,可以看上面AQS的简要,第三点

unlock()

 

public void unlock() {
    sync.release(1);
}

 

public final boolean release(int arg) {
    // 修改state值,减一
    if (tryRelease(arg)) {
        Node h = head;
  		// 获取他头结点信息 (头部结点,就是当前拥有锁的线程所拥有)
        if (h != null && h.waitStatus != 0)
 			 // 主要作用是唤醒头结点的下一个结点
            unparkSuccessor(h);
        return true;
    }
    return false;
}


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.
     */
	// 获取它的next结点
    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);
}

 

 
protected final boolean tryRelease(int releases) {
    // 减法 
    int c = getState() - releases;
	// 判断是否是执行线程去调用的unLock()
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
		// 当state=0,释放成功(锁已经空闲)
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

非公平锁(NonfairSync)

lock()

final void lock() {
     // 当线程进来之后,会直接判断当前state的值。如果是0 ,他能够直接通过
	// cas操作,设置state的值为1 的话,那么竞争锁成功
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 上面设置失败的话,那么直接调用acquire方法
        acquire(1);
}



 

 

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

 

 

调用nofairSync的nonfairTryAcquire方法

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    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;
    }
    return false;
}

 

 

通过公平锁acquire的分析,我们可以看到,在acquire方法中,会调用nonfairTryAcquire() 方法,再次竞争一次锁,

不会去判断是否是等待最久的线程,因此失去了公平性

unlock()

跟公平锁一致

 

 

总结: 

通过对公平锁的分析,在unlock的时候,会对head节点的next节点做唤醒操作, 也就是唤醒下一个节点来竞争锁,

那么体现非公平锁的特性来了, 当唤醒下一个节点来竞争锁的时候, 又有几个其他线程调用了lock方法,这个时候

另外几个线程和next节点所代表的线程都会去竞争锁,并不保证next节点能够一定获取到锁。

sharedCode源码交流群,欢迎喜欢阅读源码的朋友加群,添加下面的微信, 备注”加群“ 。 

ReentrantLock源码分析_第4张图片

 

 

 

你可能感兴趣的:(并发包)