ReentrantLock
是一个和synchronized
拥有相同语义但同时扩展了额外功能的可重入互斥锁实现。ReentrantLock
将由最近成功得到锁定,而且尚未释放该锁定的线程所拥有。当锁定没有被另外一个线程所拥有时,调用 lock()
的线程将成功获取该锁定并返回。若是当前线程已经拥有该锁定,此方法将当即返回。可使用isHeldByCurrentThread()
和 getHoldCount()
方法来检查此状况是否发生。node
ReentrantLock
有公平锁和非公平锁两种,经过构造器传入一个boolean fair
参数指定,该参数是可选的,默认为false,也就是说,默认是非公平锁实现。但请注意,这里所说的公平与非公平,只是说获取锁的时候是否顺序进行,并不保证线程调度的公平性。所以,使用公平锁的多个线程中的一个可能会连续屡次得到它。公平锁即在线程相互争用锁的状况下,它会更偏向于让等待时间最长的那个线程得到锁(队头),但相比较非公平锁,使用多个线程访问公平锁的程序吞吐量比较低,或者明显更慢。bash
一般状况下建议将释放锁的操做放置在finally{}
语句块中,以下面代码:并发
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
复制代码
查看ReentrantLock
源码,发现该类只有一个Sync
的成员变量:jvm
private final Sync sync;
复制代码
Sync
为继承自AQS
的一个同步器实现,其内部同时提供了一个lock()
抽象方法供子类实现,完成获取锁操做。Sync
有FairSync
,NonfairSync
两个子类,分别提供公平锁和非公平锁的相关操做。高并发
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
}
static final class FairSync extends Sync {}
static final class NonfairSync extends Sync {}
复制代码
下面分别从公平和非公平两种实现探讨其获取锁和释放锁的操做:源码分析
final void lock() {
acquire(1);
}
复制代码
能够看出其直接调用AQS
的acquire(int)
方法获取锁,接下来看下acquire()
实现:性能
public final void acquire(int arg) {
// 一、首先尝试获取锁,若是获取失败,那么就假如等待队列中
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
接着查看FairSync
中提供的tryAcquire(int)
方法:优化
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 一、若是锁尚未被别人获取,及同步状态为0
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;
}
复制代码
从上面代码第2步能够看出,公平锁会偏向于给队列中等待时间最长的线程优先得到锁,若是此时没有其余线程在等待,则执行CAS争抢锁资源。第3步若是该锁已经被持有,则判断持有该锁的线程是否当前线程自己,若是是,那么同步状态state递增(加锁次数)。能够看出,公平锁按等待队列顺序分配锁资源,高并发下性能,效率不高。ui
final void lock() {
// 一、直接参与竞争,若是该锁已被其它线程持有,那么就执行else
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
复制代码
接着继续查看Sync
提供的nonfairTryAcquire()
方法,源码以下:spa
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;
}
复制代码
忽然,小编发现,这个nonfairTryAcquire()
方法怎么和上面讲到的tryAcquire(int)
有点类似,原来tryAcquire(int)
比nonfairTryAcquire()
多了一步判断同步队列中是否有其它线程正在等待,所以,这也是公平和非公平的区别所在,若是nonfairTryAcquire()
方法也没能获取锁,那么将被挂到同步等待队列中。
那么,咱们可能会问,既然会被挂到同步队列中,那当前被挂起的这个线程后续是怎么被唤醒抢夺锁资源的呢?仍是按顺序出队列吗?若是仍是按顺序出队列,是否是就和公平锁同样了呢?其实很简单,仍是回到下面这个方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
首先执行tryAcquire(arg)
获取锁失败以后,会执行addWaiter()
将本身封装成Node
节点入队,接着调用acquireQueued()
方法,答案就在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 {
// 因为中断或者超时,必须将状态改为cancel
if (failed)
cancelAcquire(node);
}
}
复制代码
原来非公平锁参与锁竞争失败、被挂到等待队列以后,会cas+自旋直到获取锁成功,确实很不公平,哈哈。
public void unlock() {
sync.release(1);
}
复制代码
ReentrantLock
中释放锁统一为unlock()
方法,从上面源码能够看出,每调用一次unlock()
方法,同步状态就会减一,也就是说,lock()
多少次,就要对应unlock()
多少次。
接着深刻release()
方法,源码以下:
public final boolean release(int arg) {
// 一、尝试释放锁
if (tryRelease(arg)) {
Node h = head;
// 二、唤醒那些因为中断或其它状况致使waitStatus不为0的节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 若是线程不是当前线程持有,那么就报错
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 该锁已经没有线程持有了,同步状态为0了,那么其它线程就能够从cas+自旋中退出了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
复制代码
都说synchronized
使用的是重量级锁,性能很是低下,可是新版本的jdk已经作了如偏向锁之类的优化,性能其实还能够。synchronized
属于jvm层面的加锁机制,而Lock
属于API层面上的加锁,那么它们到底有什么区别呢?
一、如等待可中断
持有锁的线程若是长期不释放锁,正在等待的线程能够选择放弃等待。
1.设置超时方法tryLock(long timeout, TimeUnit unit)
,时间过了就放弃等待;
2.调用lockInterruptibly()
方法,若是线程中断了,则结束获取锁操做;
二、synchronized
为非公平锁,ReentrantLock
同时支持公平锁和非公平锁
三、ReentrantLock
可结合Condition
条件进行使用,可分别对多种条件加锁,对线程的等待、唤醒操做更加详细和灵活,在多个条件变量和高度竞争锁的地方,ReentrantLock
更加适合
有一点须要注意就是,释放锁的操做必定要在finally
块执行,不然可能出现死锁等意外状况。
参考文章: