ReentrantLock 构造方法:
//默认使用的是非公平的锁
public ReentrantLock() {
sync = new NonfairSync();
}public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
lock():
ReentrantLock的lock()方法实际调用的是Sync类的lock()方法,Sync继承自AQS(AbstractQueuedSynchronizer); Sync有两个子类,公平锁FairSync和非公平锁NonfairSync,Sync根据ReentrantLock实例化时是公平锁还是非公平锁来调用对应的lock()方法, 此处以非公平锁NonfairSync为例:
public void lock() {
sync.lock();
}NonfairSync:
/**
*执行锁定。尝试立即插入,在故障时备份到正常获取。
*/
final void lock() {
//采用cas将AQS中的state从0变为1
if (compareAndSetState(0, 1))
/**
* 在ReentrantLock语境下,state代表锁被重入的次数,这意味着只有当前锁未被任何线程持有时该动作才会返回成功.
* 获取锁成功后,将当前线程标记成为当前锁的线程,此时,加锁流程结束
*/
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} //AQS中的acquire()方法
//lock()失败时执行
public final void acquire(int arg) {if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// NonfairSync中的tryAcquire:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
非公平锁获取锁的方法:
//Sync中的nonfairTryAcquire:
/**
*执行不公平的tryLock。 tryAcquire是在子类中实现的,但是两者都需要非正确的try try方法。
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前线程
int c = getState();
//获取当前state的值, 即当前锁被重入的次数
if (c == 0) {//锁被重入的次数为0说明没被任何线程持有
//采用CAS获取锁
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;
}
从代码中可以看出,非公平下获取锁的情况有以下几种:
- 1.当前锁未被任何线程持有(state=0),则以cas方式获取锁,若获取成功则设置exclusiveOwnerThread为当前线程,然后返回成功的结果;若cas失败,说明在得到state=0和cas获取锁之间有其他线程已经获取了锁,返回失败结果。
- 2.若锁已经被当前线程获取(state>0,exclusiveOwnerThread为当前线程),则将锁的重入次数加1(state+1),然后返回成功结果。因为该线程之前已经获得了锁,所以这个累加操作不用同步。
- 3.若当前锁已经被其他线程持有(state>0,exclusiveOwnerThread不为当前线程),则直接返回失败结果
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//当前线程尝试获取锁,若获取成功(第一次获取锁或者重入锁)返回true,否则false
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//只有当前线程获取锁失败才会执行者这部分代码
selfInterrupt();
}
addWaiter()方法: 采用CAS将获取锁失败的线程装入队列队尾中
//将获取锁失败的线程加入队列中
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;
//采用CAS将当前节点设为队尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}//采用CAS将当前线程节点插入队列中
private Node enq(final 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;
}
}
}
}
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);
}
}
文章图片
文章图片
公平锁和非公平锁的区别
//非公平锁的lock()
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//采用cas将AQS中的state从0变为1
if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
} //公平锁的lock()
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
可见,非公平锁比公平锁多了一步尝试抢占锁的操作,抢占失败了才会将线程保存到同步队列中等待唤醒。因为队列头节点在释放锁,唤醒队列中下一个等待的节点之前,就有可能被新到来的线程获取到锁,这就是非公平的,可能会导致队列中的线程永远都获取不到锁,因为锁可能被新到来的线程抢占掉而公平锁是不允许新到来的线程抢占锁,必须要从先进先出的队列中获取,从队列中获取的线程都是离头节点最近的在等待中的节点,也就是等待时间最久的线程节点。这样是公平的
那么,为什么大多情况下使用的都是非公平锁呢 【浅谈ReentrantLock】那当然是因为非公平锁的效率比公平锁要高啊; 从上面的源码中就可以看出来, 非公平锁比公平锁多了一步抢占锁的操作, 也就是说,当抢占锁成功的时候, 可以省略掉后面那些将线程保存到队列中,并且等待唤醒的操作; 这样自然效率就高了;
解锁:unlock()
//解锁流程
public void unlock() {//解锁sync.release(1);
}public final boolean release(int arg) {if (tryRelease(arg)) {//释放锁(state-1),若释放后锁可被其他线程获取(state=0),返回trueNode h = head;
//当前队列不为空且头结点状态不为初始化状态(0)
if (h != null && h.waitStatus != 0)
//唤醒同步队列中后续被阻塞的线程
unparkSuccessor(h);
return true;
}
return false;
}//尝试释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//计算待更新的state值
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//待更新的state值为0,说明持有锁的线程未重入,一旦释放锁其他线程将能获取
free = true;
//将当前锁标记的线程清除,并将state设为0
setExclusiveOwnerThread(null);
}
//更新state值
setState(c);
return free;
}private void unparkSuccessor(Node node) {
//如果状态为负(即,可能需要信号)尝试在预期的信令中清除。如果这失败或者如果状态通过等待线程而改变,则是OK。
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, 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)
s = t;
}//唤醒离当前节点最近的且在等待中的节点
if (s != null)
LockSupport.unpark(s.thread);
}//解锁流程:先释放当前锁,判断是不是重入锁,不是则唤醒队列中离当前节点最近的在等待中的节点
推荐阅读
- 并发编程|tomcat对AQS的扩展(使用LimitLatch控制连接数)
- java|java并发编程(顺序输出A、B、C循环10次)
- 两个线程交替打印奇偶
- Java|LeetCode-1114. 按序打印(多线程)
- Java|LeetCode-1117. H2O 生成(多线程)
- Java|LeetCode-1116.打印零与奇偶数(多线程)
- JAVA8|Java8对于多线程并发的一些新支持-LongAdder
- 如何实现某个线程在其他线程执行完毕之后再执行?
- 编程题|多线程: 如何实现多个线程交替打印字符串?