ReentrantLock

在介绍AQS中,我们自定义了一个基于AQS的简单的锁,并实现了基本功能,其实JUC包的其他很多锁,都是通过类似的方式去实现了,不过功能更强大而已。下面具体的看一下ReentrantLock是怎么实现的,首先看一下它的内部类
在这里插入图片描述
可以看到它有三个内部类:抽象内部类Sync,其他两个内部类:FairSyncNonfairSync分别继承了该抽象类,毫无疑问抽象类Sync必定是继承了AbstractQueuedSynchronizerFairSyncNonfairSync从名字就可以看出分别对应着公平锁与非公平锁,他们对AQS中提供的加锁的模板方法的具体实现肯定有区别,从下面的分析中我们就可以看出来了,简单看一下源码

Sync

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

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
	 * 非公平锁加锁
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
		//如果资源没有被占用,CAS修改state,这只独占线程为当前线程
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
		//资源已被占用,当前线程是独占线程,跟新state状态
        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;
    }
	
	//释放独占锁
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
		//资源是否空闲,执行释放锁之前肯定是不空闲,默认为false
        boolean free = false;
		//如果释放一次锁后,state==0,资源为空闲状态,独占线程为null
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
		//如果释放后不为0,跟新state
        setState(c);
		//资源空闲才返回true,其他情况都返回false
        return free;
    }

    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    final boolean isLocked() {
        return getState() != 0;
    }

    /**
     * Reconstitutes the instance from a stream (that is, deserializes it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

NonfairSync

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
	 * 非公平加锁,立即执行CAS加锁,如果失败则正常加锁
     */
    final void lock() {
    	//首先执行一次CAS操作修改state状态,尝试获取锁
        if (compareAndSetState(0, 1)){
			setExclusiveOwnerThread(Thread.currentThread());
		} 
        else{
			/**
			 * 调用的是AQS的acquire()方法,父类Sync里面没有重写此方法
			 * 在AQS的acquire()中又会调用tryAcquire()
			 * 实际最后还是调用了下面的tryAcquire()方法
			 * 然后调用父类Sync的nonfairTryAcquire()方法 。。。服了
			 */
			acquire(1);
		} 
    }
	/**
     * 执行tryAcquire尝试加锁
     * 调用的是父类Sync的nonfairTryAcquire()方法
     */
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

FairSync

static final class FairSync extends Sync {
	private static final long serialVersionUID = -3000897897090466540L;
	
	/**
	 * 最终会调用下面的tryAcquire()
	 * 可以看到与非公平锁的不用之处就是没有立即进行CAS
	 * 而是执行正常的加锁操作
	 */
	final void lock() {
		acquire(1);
	}

	/**
	 * Fair version of tryAcquire.  Don't grant access unless
	 * recursive call or no waiters or is first.
	 * 公平锁加锁
	 */
	protected final boolean tryAcquire(int acquires) {
		final Thread current = Thread.currentThread();
		int c = getState();
		if (c == 0) {
			//队列中没有等待的线程节点,并且CAS操作成功,则成功获取锁
			if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
				setExclusiveOwnerThread(current);
				return true;
			}
		}
		//资源被占,占用的线程是当前线程,更新state
		else if (current == getExclusiveOwnerThread()) {
			int nextc = c + acquires;
			if (nextc < 0)
				throw new Error("Maximum lock count exceeded");
			setState(nextc);
			return true;
		}
		return false;
	}
}
  • hasQueuedPredecessors()
    在非公平锁中没有这一个判断,直接进行CAS修改state状态
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;
		//当头尾节点不重合 && (头结点的下个节点为null || 头结点的下个节点不是当前线程节点)
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

在这里插入图片描述

ReentrantLock的构造方法及成员变量

private final Sync sync;

public ReentrantLock() {
    // 默认非公平锁
    sync = new NonfairSync();
}
//根据参数创建公平锁或者非公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

ReentrantLock#lock

调用sync.lock()方法,即根据锁的类型,分别调用FairSync和NonfairSync的lock方法(过程见上面的分析)

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

ReentrantLock的其他大部分方法也都是通过Sync的实现类对象来调的(FairSync或者NonfairSync的对象,还有些方法是直接定义在AQS中)

ReentrantLock加锁过程分析

非公平锁加锁过程
  • 1、ReentrantLock#lock()
public void lock() {
    sync.lock();
}
  • 2、NonfairSync#lock()
final void lock() {
    //首先执行一次CAS操作修改state状态,尝试获取锁
    if (compareAndSetState(0, 1)){
        setExclusiveOwnerThread(Thread.currentThread());
    }
    else{
        /**
         * 调用的是AQS的acquire()方法,父类Sync里面没有重写此方法
         * 在AQS的acquire()中又会调用tryAcquire()
         * 实际最后还是调用了下面的tryAcquire()方法
         * 然后调用父类Sync的nonfairTryAcquire()方法 。。。服了
         */
        acquire(1);
    }
}

在2方法中获取锁时,首先对同步状态执行CAS操作(compareAndSetState(0, 1)),尝试把state的状态从0设置为1,如果返回true则代表获取同步状态成功,也就是当前线程获取锁成,可操作临界资源;如果返回false,则表示已有线程持有该同步状态(其值不为0),获取锁失败,注意这里存在并发的情景,也就是可能同时存在多个线程设置state变量,因此是CAS操作保证了state变量操作的原子性。返回false后,执行 acquire(1)方法,该方法是AQS中的方法,它对中断不敏感,即使线程获取同步状态失败,进入同步队列,后续对该线程执行中断操作也不会从同步队列中移出,方法如下

  • 3、AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    //再次尝试获取同步状态
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
    	selfInterrupt();
    }
}

进入方法后首先会执行tryAcquire(arg)方法,在前面分析过该方法在AQS中并没有具体实现,而是交由子类实现,即ReetrantLock类内部NonfairSync实现的

  • 4、ReentrantLock.NonfairSync#tryAcquire
/**
 * 执行tryAcquire尝试加锁
 * 调用的是父类Sync的nonfairTryAcquire()方法
 */
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
  • 5、调用父类:Sync#nonfairTryAcquire()
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 * 非公平锁加锁
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //如果资源没有被占用,CAS修改state,设置独占线程为当前线程
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //资源已被占用,当前线程是独占线程,更新state状态
    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;
}

首先获取状态变量,当在c=0 即(state=0)时,利用CAS修改状态变量,如果成功则将当前线程设置为独占线程(setExclusiveOwnerThread),如果CAS失败,返回false。如果state不为0,即已经有线程抢占了当前资源,则判断当前线程current是否为已独占资源的线程,如果是则属于重入锁,state自增1,并获取锁成功,返回true,反之失败,返回false,也就是tryAcquire(arg)执行失败,返回false。需要注意的是nonfairTryAcquire(int acquires)内部使用的是CAS原子性操作设置state值,可以保证state的更改是线程安全的,因此只要任意一个线程调用nonfairTryAcquire(int acquires)方法并设置成功即可获取锁,不管该线程是新到来的还是已在同步队列的线程,毕竟这是非公平锁,并不保证同步队列中的线程一定比新到来线程请求(可能是head结点刚释放同步状态然后新到来的线程恰好获取到同步状态)先获取到锁,这点跟公平锁不同。回到3、AbstractQueuedSynchronizer#acquire

public final void acquire(int arg) {
    //再次尝试获取同步状态
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
    	selfInterrupt();
    }
}

addWaiter(Node.EXCLUSIVE)如果执行到此方法, 说明前面尝试获取锁的tryAcquire已经失败了, 既然获取锁已经失败了, 就要将当前线程包装成Node,加到等待锁的队列中去, 因为是FIFO队列, 所以自然是直接加在队尾。由于ReentrantLock属于独占锁,因此参数结点类型为Node.EXCLUSIVE,下面看看addWaiter()方法具体实现

  • 6、AbstractQueuedSynchronizer#addWaiter
private Node addWaiter(Node mode) {
	//6.1创建当前线程节点
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //创建pred节点指向尾节点
    Node pred = tail;
    //6.2尾节点不为null时,第一次执行该方法的时候,tail head 都为 null
    if (pred != null) {
    	//让当前线程节点的前驱节点指向pred节点(尾节点)
        node.prev = pred;
        //CAS交换pred和node节点
        if (compareAndSetTail(pred, node)) {
        	//pred的后继节点指向node
            pred.next = node;
            //返回当前线程节点
            return node;
        }
    }
    //7、注意!到达这里有两种情况,一是尾节点为null(队列为空)
    //二是CAS替换尾节点失败(因为这个方法是多个线程一起执行的,CAS可能成功,可能失败)
    enq(node);
    //返回当前线程节点
    return node;
}

6.1使用当前线程、和当前模式(独占),创建了一个Node节点,可以看到addWaiter(Node mode)的参数在3方法中传过来的是Node.EXCLUSIVE,定义在AQS类中,值为null,所以创建Node时使用的构造器如下

Node(Thread thread, Node mode) {     // Used by addWaiter
	//nextWaiter为null
    this.nextWaiter = mode;
    this.thread = thread;
}
/**
 * 从这个方法可以看出,如果是共享模式,则nextWaiter不为null(SHARED不为null)
 * 如果是独享模式,则nextWaiter永远为null
 * 由于此时的锁为独占锁,所以在上面的构造函数中传递的 mode为 null
 * Returns true if node is waiting in shared mode.
 */
final boolean isShared() {
    return nextWaiter == SHARED;
}

这里来看一下6.2下面的代码

if (pred != null) {
	//让当前线程节点的前驱节点指向pred节点(尾节点)
    node.prev = pred;
    //CAS交换pred和node节点
    if (compareAndSetTail(pred, node)) {
    	//pred的后继节点指向node
        pred.next = node;
        return node;
    }
}

在这里插入图片描述
上面if判断为true执行的代码可以看成下面三个步骤:

  1. 设置node的前驱节点为当前的尾节点:node.prev = pred
  2. 修改tail属性,使它指向当前节点
  3. 修改原来的尾节点,使它的next指向当前节点
    所以,当有大量的线程在同时入队的时候,同一时刻,只有一个线程能完整地完成这三步,而其他线程只能完成第一步,于是就出现了尾分叉
    在这里插入图片描述

注意,这里第三步是在第二步执行成功后才执行的,这就意味着,有可能即使我们已经完成了第二步,将新的节点设置成了尾节点,此时原来旧的尾节点的next值可能还是null(因为还没有来的及执行第三步),所以如果此时有线程恰巧从头节点开始向后遍历整个链表,则它是遍历不到新加进来的尾节点的,但是这显然是不合理的,因为现在的tail已经指向了新的尾节点。
另一方面,当我们完成了第二步之后,第一步一定是完成了的,所以如果我们从尾节点开始向前遍历,已经可以遍历到所有的节点。这也就是为什么我们在AQS相关的源码中,有时候常常会出现从尾节点开始逆向遍历链表——因为一个节点要能入队,则它的prev属性一定是有值的,但是它的next属性可能暂时还没有值。

至于那些“分叉”的入队失败的其他节点,在下一轮的循环中,它们的prev属性会重新指向新的尾节点,继续尝试新的CAS操作,最终,所有节点都会通过自旋不断的尝试入队,直到成功为止。

  • 7、AbstractQueuedSynchronizer#enq
    当tail==null的时候,即队列还未初始化的时候
// node为当前线程节点
private Node enq(final Node node) {
	// 死循环
    for (;;) {
    	//尾节点赋值给t
        Node t = tail;
        //如果尾节点为null,则为空队列
        //从这里可以看出, 队列不是在构造的时候初始化的, 而是延迟到需要用的时候再初始化
        if (t == null) { // Must initialize,初始化
            if (compareAndSetHead(new Node())) //生成一个空节点作为头部
            	// 将尾节点指向空节点(head),继续循环,下次循环的时候 t != null,走下面的else
                tail = head;
        } else {
        	// 到这里说明队列已经不是空的了, 这个时候再继续尝试将节点加到队尾
            node.prev = t;
            //交换尾节点和当前节点
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

要注意的是,当队列为空时,我们初始化队列并没有使用当前传进来的节点,而是新建了一个空节点!在新建完空的头节点之后,我们并没有立即返回,而是将尾节点指向当前的头节点,然后进入下一轮循环。
在下一轮循环中,尾节点已经不为null了,此时再将我们包装了当前线程的Node加到这个空节点后面。
这就意味着,在这个等待队列中,头结点是一个“哑节点”,它不代表任何等待的线程。

至此,我们就完成了6、addWaiter(Node.EXCLUSIVE)方法的完整的分析,该方法并不设计到任何关于锁的操作,它就是解决了并发条件下的节点入队问题。具体来说就是该方法保证了将当前线程包装成Node节点加入到等待队列的队尾,如果队列为空,则会新建一个哑节点作为头节点,再将当前节点接在头节点的后面。

6、addWaiter(Node.EXCLUSIVE)方法最终返回了代表了当前线程的Node节点,在返回的那一刻,这个节点必然是当时的sync queue的尾节点。

不过值得注意的是,7、enq()方法也是有返回值(虽然这里我们并没有使用它的返回值),但是它返回的是node节点的前驱节点,这个返回值虽然在6-addWaiter()方法中并没有使用,但是在其他地方会被用到。回到3、AbstractQueuedSynchronizer#acquire

public final void acquire(int arg) {
    //再次尝试获取同步状态
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
    	selfInterrupt();
    }
}
  • 8、AbstractQueuedSynchronizer#acquireQueued
    当前线程在自旋(死循环)中获取同步状态,当且仅当前驱结点为头结点才尝试获取同步状态,这符合FIFO的规则,即先进先出,其次head是当前获取同步状态的线程结点,只有当head释放同步状态唤醒后继结点,后继结点才有可能获取到同步状态,因此后继结点在其前继结点为head时,才进行尝试获取同步状态,其他时刻将被挂起。
//node为尾节点,arg为1
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取尾节点的前驱结点
            final Node p = node.predecessor();
            // 尾节点的前驱节点p就是HEAD节点时(尾节点node为队列中第一位置的线程节点) 再次尝试获取锁
            // 符合队列先进先出的原则,也就是队列最前面的,先进来的最先尝试获取锁
            // 如果p是头结点,并且获取锁成功(修改state,设置独占线程成功)
            if (p == head && tryAcquire(arg)) {
            	//将node设置为头结点
                setHead(node);
                //原来头结点的next设置为null
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //如果当前尾节点的前驱节点不是head节点,或者尝试获取锁失败
            //判断是否需要把当前线程挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

如果前驱节点就是head节点,那说明当前线程已经排在了队列的最前面,所以这里我们再试着去获取锁。如果这一次获取成功了,即tryAcquire方法返回了true, 则将进入 if 代码块,调用setHead方法:
8.1、AbstractQueuedSynchronizer#setHead

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

在这里插入图片描述
可以看出,这个方法的本质是丢弃原来的head,将head指向已经获得了锁的node。但是接着又将该node的thread属性置为null了,这某种意义上导致了这个新的head节点又成为了一个哑节点,它不代表任何线程。为什么要这样做呢,因为在tryAcquire调用成功后,exclusiveOwnerThread属性就已经记录了当前获取锁的线程了,此处没有必要再记录。这某种程度上就是将当前线程从等待队列里面拿出来了,是一个变相的出队操作。 这里的setHead操作并没有使用CAS机制,是因为此时已经通过tryAcquire获得了锁

8.2、AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire
在上面的if判断为false的时候,进入下面的if判断,首先就是执行这个方法,从函数名也可以看出, 该方法用于决定在获取锁失败后, 是否将线程挂起。在这个方法中主要是对Node节点里的waitStatus进行判断,在独占模式下,这里只会用到下面两个状态:

  • static final int CANCELLED = 1 //标识线程已处于结束状态
  • static final int SIGNAL = -1 //等待被唤醒状态

当然,前面我们在创建节点的时候并没有给waitStatus赋值,因此每一个节点最开始的时候waitStatus的值都被初始化为0,即不属于上面任何一种状态。
CANCELLED:它表示Node所代表的当前线程已经取消了排队,即放弃获取锁了。
SIGNAL:当一个节点的waitStatus被置为SIGNAL,就说明它的下一个节点(即它的后继节点)已经被挂起了(或者马上就要被挂起了),因此在当前节点释放了锁或者放弃获取锁时,如果它的waitStatus属性为SIGNAL,它还要完成一个额外的操作——唤醒它的后继节点。

比如说出去吃饭,在人多的时候经常要排队取号,你取到了8号,前面还有7个人在等着进去,你就和排在你前面的7号讲,哥们,我现在排在你后面,队伍这么长,估计一时半会儿也轮不到我,我去那边打个盹,一会轮到你进去了(release)或者你不想等了(cancel),麻烦你都叫醒我”,说完,你就把他的waitStatus值设成了SIGNAL。

换个角度讲,当我们决定要将一个线程挂起之前,首先要确保自己的前驱节点的waitStatusSIGNAL,这就相当于给自己设一个闹钟再去睡,这个闹钟会在恰当的时候叫醒自己。

 /** waitStatus value to indicate thread has cancelled */
 static final int CANCELLED =  1;
 /** waitStatus value to indicate successor's thread needs unparking */
 static final int SIGNAL    = -1;
 /** waitStatus value to indicate thread is waiting on condition */
 static final int CONDITION = -2;
 /**
  * waitStatus value to indicate the next acquireShared should
  * unconditionally propagate
  */
 static final int PROPAGATE = -3;


//pred节点是node的前驱节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	//获取前驱节点的waitStatus
    int ws = pred.waitStatus;
    //如果前驱节点的ws为SIGNAL了,则当前节点的线程可以被挂起了
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         * 当前节点的 ws > 0, 则为 Node.CANCELLED 说明前驱节点已经取消了等待锁(由于超时或者中断等原因)
         * 既然前驱节点不等了, 那就继续往前找, 直到找到一个还在等待锁的节点
         * 然后我们跨过这些不等待锁的节点, 直接排在等待锁的节点的后面
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0); // 小于等于0(不是SIGNAL)的时候跳出循环
        pred.next = node;
    } else {
        /*
         * 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.
         * 前驱节点的状态既不是SIGNAL,也不是CANCELLED
         * CAS设置前驱节点的 ws为 Node.SIGNAL,给自己定一个闹钟
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

可以看出,shouldParkAfterFailedAcquire所做的事情无外乎:

  • 如果为前驱节点的waitStatus值为 Node.SIGNAL 则直接返回 true,标识可以将当前线程挂起
  • 如果前驱节点的状态为CANCELLED,说明前驱节点本身不再等待了,需要跨越这些节点,然后找到一个有效节点,再把node和这个有效节点的前驱后继连接好。
  • 其他情况, 将前驱节点的状态改为 Node.SIGNAL,返回false

注意了,这个函数只有在当前节点的前驱节点的waitStatus状态本身就是SIGNAL的时候才会返回true,其他时候都会返回false,我们再回到这个方法的调用处:

if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()){
	interrupted = true;
}        

当如果shouldParkAfterFailedAcquire()方法返回false,会继续执行循环

shouldParkAfterFailedAcquire()返回true,即当前节点的前驱节点的waitStatus状态已经设为SIGNAL后,我们就可以安心的将当前线程挂起了,此时我们将调用parkAndCheckInterrupt()
8.4、AbstractQueuedSynchronizer#parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
	//挂起当前线程
    LockSupport.park(this);
    //返回线程的中断状态
    return Thread.interrupted();
}

那么使用parkAndCheckInterrupt()方法挂起当前线程,称为WAITING状态,需要等待一个unpark()操作来唤醒它,到此ReetrantLock内部间接通过AQS的FIFO的同步队列就完成了lock()操作

ReentrantLock释放锁程分析

public void unlock() {
    sync.release(1);
}
//直接调用AQS中的release()
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        //如果此时头节点不为null,并且waitStatus不为0
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
1、ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
    // 这里的操作主要是针对可重入锁的情况下, c可能大于1
    int c = getState() - releases;
    // 释放锁的线程当前必须是持有锁的线程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果c为0了, 说明锁已经完全释放了
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
2、AbstractQueuedSynchronizer#unparkSuccessor

锁成功释放之后, 接下来就是唤醒后继节点了

//这里的参数Node为头结点Head
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){
    	//设置头结点waitStatus为0
    	compareAndSetWaitStatus(node, ws, 0);
    }
    /*
     * 通常情况下, 要唤醒的节点就是自己的后继节点
     * 如果后继节点存在且也在等待锁, 那就直接唤醒它
     * 但是有可能存在 后继节点取消等待锁 的情况
     * 此时从尾节点开始向前找起, 直到找到距离head节点最近的ws<=0的节点
     */
    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){
        		 // 注意! 这里找到了之并有跳出循环或者return, 而是继续向前找
        		s = t;
        	}
        }    
    }
    // 如果找到了还在等待锁的节点,则唤醒它
    if (s != null)
        LockSupport.unpark(s.thread);
}

剩余源码有空再补。。。

synchronized和ReentrantLock 的区别

① 两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

③ ReentrantLock 比 synchronized 增加了一些高级功能

  • 等待可中断
    ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

  • 可实现公平与非公平锁
    ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。

  • 可实现选择性通知(锁可以绑定多个条件)
    synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值