jdk8 ReentrantLock源码注释解析

1 篇文章 0 订阅
1 篇文章 0 订阅

1 概述

AQS是一个基于先进先出的等待队列来实现的锁和同步器这么一个框架。

在这里插入图片描述
发现一个很好的源码学习工具的网站 https://codecommenter.cn,可以记录自己添加的注释,进行源码学习。里面一些功能需要自己发现,并没有引导页面,满足你的探索欲。

2 从ReentrantLock加锁的方法开始分析源码

// ****************************************************************************************
// ***  ReentrantLock公平锁的lock
// ****************************************************************************************
/*
 * java.util.concurrent.locks.ReentrantLock.FairSync#lock
 * 
 * ReentrantLock公平锁的lock方法
 */
final void lock() {
	// 上来调用acquire方法
	acquire(1);
}

这里就 是直接调用了AQS里的acquire方法,这就是为什么ReentrantLock是基于AQS实现的,它自己上来就调人家的方法。

/**
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
 *
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
	/*
	 * acquire是AQS的方法,该方法没有自己的实现逻辑,而是通过调用其它的方法来
	 * 这个方法做了三件事
	 * 1 tryAcquire 
	 *   试着抢一下锁
	 *   抢到: 返回
	 *   没抢到: 执行第二件事
	 * 2 addWaiter 把自己封装成Node对象,并将当前线程加入到队列尾部
	 * 3 acquireQueued 把自己加到队列并不是目的,在队列里,是要等待锁资料的。所以第三件事就是当自己在队列里的时候,侍机抢一下锁资源。
	 */
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		/*
		 * 只有线程被中断的时候,acquireQueued才会返回true,因此也才会执行selfInterrupt()方法
		 * 如果线程没有被中断,在acquireQueued里排上队,总有一天会获取到锁,最终返回false
		 * 这个方法就消无声息地执行完了
		 */
		selfInterrupt();
}

tryAcquire是AQS里定义的方法,但在AQS里,该方法默认实现是恒抛出了一个异常,意味着该方法的逻辑应该由实现类去实现。这是当然的,因为应该怎样获取锁,本来就应该是实现类自己去做的。比如现在的ReentrantLock来说,你觉得对state做怎样的操作后,就表示获取了锁,当然是你ReentrantLock自己去决定了,我给你提供一个这样的变量就很好了。
在ReentrantLock里,把state从0变成1,表示获取了锁,如果重入的话,很次对state都加1,这是ReentrantLock里对state的操作。还有不是这样的?读写锁就不是这样。

/**
 * 
 * AQS里的tryAcquire方法
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
 *
 *
 * Attempts to acquire in exclusive mode. This method should query
 * if the state of the object permits it to be acquired in the
 * exclusive mode, and if so to acquire it.
 *
 * <p>This method is always invoked by the thread performing
 * acquire.  If this method reports failure, the acquire method
 * may queue the thread, if it is not already queued, until it is
 * signalled by a release from some other thread. This can be used
 * to implement method {@link Lock#tryLock()}.
 *
 * <p>The default
 * implementation throws {@link UnsupportedOperationException}.
 *
 * @param arg the acquire argument. This value is always the one
 *        passed to an acquire method, or is the value saved on entry
 *        to a condition wait.  The value is otherwise uninterpreted
 *        and can represent anything you like.
 * @return {@code true} if successful. Upon success, this object has
 *         been acquired.
 * @throws IllegalMonitorStateException if acquiring would place this
 *         synchronizer in an illegal state. This exception must be
 *         thrown in a consistent fashion for synchronization to work
 *         correctly.
 * @throws UnsupportedOperationException if exclusive mode is not supported
 */
protected boolean tryAcquire(int arg) {
	throw new UnsupportedOperationException();
}
/*
 * java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
 * 
 *
 * ReentrantLock里公平锁对tryAcquire的实现
 */
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		/* 
		 * c == 0 说明这会没有线程持有锁,但这时并不能就放心地去拿锁了
		 * 因为就在判断完 c == 0 以后,当前线程还没来得及拿锁(将state由0变为1),可能有其它线程
		 * 就抢先拿了锁,修改了state的值
		 * 要不怎么说是并发呢
		 */
		 /*
		  * hasQueuedPredecessors用来判断当前是否已经有线程在排队
		  * 如果这个方法返回true,说明已经有线程在排队了,那自己肯定也只有排队了
		  * 因为是公平锁嘛
		  * 
		  * 自己刚判断完c==0,这里就已经排上队了,说明当前线程判断完c==0后,可能休息了很长很长时间
		  * 这个世界已经变得跟以前不一样了。
		  */
		if (!hasQueuedPredecessors() &&
		    // 如果没有人在排队,那我刚判断完c==0,现在还没有纯种拿着锁,那我就赶快抢一把呗
			compareAndSetState(0, acquires)) {
				/*
				 * 进入到if语句块,说明compareAndSetState方法执行成功了,当前线程抢到了锁
				 * 于是把AQS里表示哪个线程持有锁的变量,赋值成自己这个线程
				 */
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	// c != 0,说明当前有线程持有锁呢正,那我判断一下这个锁是不是我自己拿着呢,即当前线程
	else if (current == getExclusiveOwnerThread()) {
		/*
		 * 发现当前拿着锁的线程是我自己,说明我是重入操作,这时候把state+1就OKAY了
		 * 不过需要注意的是,state是一个int类型的变量,这是有最大值(2147483647)的
		 * 如果一直重入一直加1,可能会超过这个最大值,所有判断一下有没有越界
		 */
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

如果tryAcquire方法返回 true,也就是说抢到了锁,那么acquire方法里if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))这一行判断就结果了,抢到锁直接返回。
如果tryAcquire返回false,说明没抢到锁,这时就要把当前线程封装成Node对象,去排队。然后在队列里等待被唤醒,待机再去抢一下锁。addWaiter方法就是把自己封装成Node对象去排队的逻辑。
加入到队列后,再执行acquireQueued方法,这个方法里有个死循环,在这个循环里排队的线程会被重复唤醒,再挂起,再唤醒,再挂起。直到线程抢到锁。

/**
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter 
 *
 *
 * 如果tryAcquire失败,会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),即要去排队了。
 * 这个addWaiter就是把当前线程封装成一个Node对象
 * 
 * Creates and enqueues node for current thread and given mode.
 *
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);
	// Try the fast path of enq; backup to full enq on failure
	
	/*
	 * 这个是自己加入到队列之前的队尾,如果自己加入到了队尾,那么之前那个队尾就是上一个节点
	 * 了,可能是因为这个所以把这会这个队尾变量名叫做是pred
	 */
	Node pred = tail;
	// 如果现在这个队尾不是null,
	if (pred != null) {
		/*
		 * 在这里把自己的上一个节点指向之前的那个队尾
		 * 不过这里并不能高兴得太早,这里只是先指了一下,并没有成定论呢。因为在此期间,可能已经有别的线程成了队尾,这里那个新的队尾的node.prev也是上面刚说到的这个队尾,即pred变量。
		 * 如果这里的操作是让上一个队尾的next马上就指向自己,那就坏了事了,因为自己还没有真正地成为新的队尾呢
		 */
		node.prev = pred;
		/*
		 * 还是刚才说到的原因,因为有并发存在,所以想要真正地成为队尾,即抢一下占住队尾位置才行。
		 */
		if (compareAndSetTail(pred, node)) {
			/*如果抢到了,这时可以放心地让之前的那个队尾的node.next指向自己了*/
			pred.next = node;
			return node;
		}
	}
	```
	/*
	 * 如果 pred != null 判断是false,说明现在还没有队尾,那么自己就要抢一下队尾的位置
	 * 或者在上面if语句块里,自己抢队尾失败,也会走到这里
	 * 即使在if语句块里抢队尾位置时没抢到,那也不能就这么算了,不管怎样,到头来都是要把自己加到队尾里去了。
	 * 这就要看一下enq里到底是个什么逻辑了
	 */
	enq(node);
	return node;
}


/**
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#enq
 *
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) {
	/*
	 * 这里是个死循环,铁了心要把自己当前线程(已经封装成node对象)添加到队尾去。
	 */
	for (;;) {
		Node t = tail;
		if (t == null) { // Must initialize
			if (compareAndSetHead(new Node()))
				tail = head;
		} else {
			node.prev = t;
			if (compareAndSetTail(t, node)) {
				t.next = node;
				return t;
			}
		}
	}
}


/**
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
 *
 * Acquires in exclusive uninterruptible mode for thread already in
 * queue. Used by condition wait methods as well as acquire.
 *
 * @param node the node
 * @param arg the acquire argument
 * @return {@code true} if interrupted while waiting
 */
final boolean acquireQueued(final Node node, int arg) {
	boolean failed = true;
	try {
		boolean interrupted = false;
		for (;;) {
			/*
			 * 能进入到这个方法,说明自己已经在队列里了(当前,是针对ReentrantLock来说的)
			 * 这里就看一下自己的上一个节点是不是头节点,如果是,就试着抢一下锁。
			 * 为什么发现自己的上一个节点是头节点,就要试着去抢一下锁呢?
			 * 这个问题可以从反面去想一个: 现在我自己(当前线程)已经加入到队列里了,但我总不能死在队列里吧,我总要找个
			 * 机会去抢一下锁,抢到锁后赶紧离开这。那这个机会定在什么时候比较好呢?
			 * 假设这个机会定在队尾,就是说发现自己是队尾的时候,就去抢一下锁。那可好了,如果一直有新的线程加入到队列里,那么靠前的
			 * 线程就可能永远拿不到锁了。
			 * 假设这个机会定在倒数第二个位置,倒数第三个,倒数第四个... ...都不合适,都会发生一样的问题。
			 * 那就好了,那就把这个机会定在,如果发现自己是正数第二个,即自己的上一个节点是头节点的时候,就去抢锁,这就合适了。
			 *
			 * 如果自己不是第二个节点,那自己就会去挂起线程了。别忘了,这里是个死循环,如果有一天自己被其它线程唤醒,也还是在这个死循环里。
			 * 这时会再次判断 if (p == head && tryAcquire(arg)) 一直到前面线程都执行完走了,到自己成为第二个节点,就有机会去试着抢一下锁了。
			 */
			final Node p = node.predecessor();
			if (p == head && tryAcquire(arg)) {
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return interrupted;
			}
			
			/* 
			 * 【Otherwise the thread is queued, possibly
             * repeatedly blocking and unblocking, invoking {@link #tryAcquire} until success.】
			 * 上面是acquire方法的一段doc注释,说自己没有抢到锁的情况下,会把自己加到队列里,然后重复地去 阻塞-解阻塞-调tryAcquire   阻塞-解阻塞-调tryAcquire
			 * 一直到获取锁成功为止。
			 * 下面这个就是重复地去 阻塞-解阻塞-调tryAcquire
			 * 
			 * 上面发现自己还不是第二个节点,于是在这里阻塞了。有一天其它线程把自己唤醒了,于是在死循环里再次执行 if (p == head && tryAcquire(arg)) -- 完成了一次阻塞-解阻塞-调tryAcquire
			 * 如果这一次成功了,就结束,如果没成功,于是在这里又阻塞了,又到有一天其它线程把自己唤醒,于是在死循环里再次执行 if (p == head && tryAcquire(arg)) -- 又完成了一次阻塞-解阻塞-调tryAcquire
			 * 一直到有一天自己获取了锁为止
			 * 
			 */
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		/*
		 * try语句块里是个死循环,所以只有程序发生异常时,才会走到这里来。
		 */
		if (failed)
			cancelAcquire(node);
	}
}

3 释放锁操作

/** 
 * 释放锁操作
 * java.util.concurrent.locks.ReentrantLock#unlock
 * 
 * Attempts to release this lock.
 *
 * <p>If the current thread is the holder of this lock then the hold
 * count is decremented.  If the hold count is now zero then the lock
 * is released.  If the current thread is not the holder of this
 * lock then {@link IllegalMonitorStateException} is thrown.
 *
 * @throws IllegalMonitorStateException if the current thread does not
 *         hold this lock
 */
public void unlock() {
	sync.release(1);
}

把关键信息写在了注释里
比较重要的是线程的唤醒逻辑。

/**
 * 在AQS里
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#release
 * 
 * Releases in exclusive mode.  Implemented by unblocking one or
 * more threads if {@link #tryRelease} returns true.
 * This method can be used to implement method {@link Lock#unlock}.
 *
 * @param arg the release argument.  This value is conveyed to
 *        {@link #tryRelease} but is otherwise uninterpreted and
 *        can represent anything you like.
 * @return the value returned from {@link #tryRelease}
 */
public final boolean release(int arg) {
	if (tryRelease(arg)) {
		/*
		 * 如果tryRelease返回true,说明释放锁已经释放干净,这时就要去唤醒其它线程了
		 */
		Node h = head;
		if (h != null && h.waitStatus != 0)
			// 去唤醒其它线程 successor是继承人的意思
			unparkSuccessor(h);
		return true;
	}
	return false;
}


/**
 * 和tryAcquire方法相似,tryRelease方法在AQS里也是直接抛出了一个异常
 * 也就是说这期望实现在自己在tryRelease。当前这很合理,在获取锁时,是实现类自己的逻辑,那释放
 * 锁当然也应该由实现类自己来释放了。
 *
 *
 * Attempts to set the state to reflect a release in exclusive
 * mode.
 *
 * <p>This method is always invoked by the thread performing release.
 *
 * <p>The default implementation throws
 * {@link UnsupportedOperationException}.
 *
 * @param arg the release argument. This value is always the one
 *        passed to a release method, or the current state value upon
 *        entry to a condition wait.  The value is otherwise
 *        uninterpreted and can represent anything you like.
 * @return {@code true} if this object is now in a fully released
 *         state, so that any waiting threads may attempt to acquire;
 *         and {@code false} otherwise.
 * @throws IllegalMonitorStateException if releasing would place this
 *         synchronizer in an illegal state. This exception must be
 *         thrown in a consistent fashion for synchronization to work
 *         correctly.
 * @throws UnsupportedOperationException if exclusive mode is not supported
 */
protected boolean tryRelease(int arg) {
	throw new UnsupportedOperationException();
}

/**
 * java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
 */
protected final boolean tryRelease(int releases) {
	int c = getState() - releases;
	/*
	 * 来释放锁了,结果发现现在持有锁的线程不是当前线程
	 * 为什么用这样?
	 * 正常情况下当然不会这样,不过想象一下,我们一不获取锁的情况下能不能调lock.unlock()方法,当然是可以的;或者在
	 * 释放时多调了几次lock.unlock(),所以
	 * 当出现以下编码时:
		1 ->
		ReentrantLock lock = new ReentrantLock();
		lock.unlock();
		
		2 -> 
		ReentrantLock lock = new ReentrantLock();
		lock.lock();
		lock.unlock();
		lock.unlock();
		
	 * 这时 Thread.currentThread() != getExclusiveOwnerThread() 就会是true
	 */
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {
		// 如果释放干净了,就返回true,把持有锁的当前线程的标识置为null,之后就会有其它线程去抢锁
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}


/**
 * 唤醒继承人的逻辑
 * java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
 * 
 * Wakes up node's successor, if one exists.
 *
 * @param node the node
 */
private void unparkSuccessor(Node node) {
	/*
	 * 这里的形参node就是当前正持有锁的线程,也是head变量对应的线程
	 * 
	 * 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);

	/*
	 * traverse: 遍历的意思
	 * 
	 * 有很多说法在唤醒线程时,是从后向前找可唤醒的节点。这个问题被广泛讨论,以至于会误认为在唤醒线程时,只是从后向前找这样。
	 * 其实这是片面的正确,在从后前找之前,会先看看自己的下一个节点是不是可唤醒(which is normally just the next node)。如果可以,当然就是直接唤醒最好了,何必还从
	 * 后向前找呢?
	 * 如果第二个节点不可唤醒(But if cancelled or apparently null.即第二个节点是null,或waitStatus是cancelled),这时候就从后向前找可唤醒的节点。
	 * 倒不是说为什么从后向前找,而是只能从后向前找了。因为第二个节点是null,或waitStatus是cancelled,这时不从后向前找还能怎样呢,还有什么别的办法吗?
	 *
	 * But if cancelled or apparently null,traverse backwards from tail to find the actual non-cancelled successor
	 * (但是如果是cancelled状态或是为null,则从后向前遍历,以此来找到一个直接的非cancelled的继承人)
	 *
	 * 这里的这个But好像有那么一点无奈不觉得吗?
	 * 
	 * 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.
	 */
	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);
}

Over

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值