ReentrantLock详解
在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock.synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的。Lock在硬件层面依赖CPU指令,完全由Java代码完成,底层利用LockSupport类和Unsafe类进行操作;虽然锁有很多实现,但是都依赖AbstractQueuedSynchronizer类,我们用ReentrantLock进行讲解
ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字相比,它更灵活、更强大,增加了轮询、超时、中断等高级功能。ReentrantLock,顾名思义,它是支持可重入锁的锁,是一种递归无阻塞的同步机制。除此之外,该锁还支持获取锁时的公平和非公平选择
ReentrantLock的类图如下:
ReentrantLock的内部类Sync继承AQS,分为公平所FairSync和非公平锁NonfairSync。如果再绝对时间上,先进入队列的会先获得锁。公平锁的获取,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。然而ReentrantLock默认是非公平锁。为什么呢?因为线程阻塞需要操作系统的调度,从内核态、用户态有各自的操作空间,挂起到线程真正运行存在严重的延迟。所以,公平所往往没有非公平所的吞吐量高。但是,公平锁能够减少“饥饿”发生的概率,等待越久的请求能够得到优先的满足。
获取锁
ReentrantLock的默认构造函数是非公平锁:
public ReentrantLock() {
sync = new NonfairSync();
}
获取锁
public void lock() {
sync.lock();
}
Sync类是ReentrantLock的内部类,继承自AQS,有两个子类:非公平锁NonfairSync和公平锁FairSync,ReentrantLock的获取和释放锁的操作都是委托给该类来实现。
非公平锁NonfairSync的lock()方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
首先会通过compareAndSetState CAS操作方法来修改同步状态,如果修改成功则表示获取到了锁,然后调用setExclusiveOwnerThread(Thread)方法来设置获取到锁的线程,该方法是AbstractOwnableSynchronizer类,AQS继承AOS,其主要作用就是记录获取到独占所的线程:
private static final long serialVersionUID = 3737899427754241961L;
protected AbstractOwnableSynchronizer() { }
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
如果同步状态修改失败,则表示没有获取到锁,需要调用AQS的acquire(int)方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(int)是子类需要重写的方法,在非公平锁的实现中的实现如下;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取同步状态
int c = getState();
//没有线程获取到锁
if (c == 0) {
//修改同步状态
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//同步状态不为0,表示已经有线程获取了锁,判断是否是当前线程获取的锁
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(int)方法首先判断同步状态是否为0,如果是0,则表示该锁还没有被线程持有,然后通过CAS操作获取同步状态,如果修改成功,返回true。如果同步状态不为0,则表示该锁已经被线程持有,需要判断当前线程是否为获取锁的线程,如果是则获取锁,成功返回true。成功获取锁的线程再次获取该锁,只是增加了同步状态的值,这也就实现了可重入锁。
释放锁
在获取到锁并完成任务后,需要调用unlock()来释放锁
public void unlock() {
sync.release(1);
}
unlock调用NonfairSync类的release(int)方法释放锁,release方法是定义在AQS中的:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(int)是子类需要实现的方法:
protected final boolean tryRelease(int releases) {
//计算状态值
int c = getState() - releases;
//判断当前线程是否持有线程的锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果计算后的值为0,表示该锁已经完成释放,其他线程可以获取该锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//更新状态值
setState(c);
return free;
}
如果该锁被获取n次,那么前(n-1)次tryRelease(int)方法必须返回false,只有同步状态完全释放了,才能返回true。可以看到,该方法将同步状态是否为0作为最终释放的条件,当状态为0时,将占有线程设为null,并返回true,表示释放成功
公平锁
公平锁的tryAcquire(int)方法:
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁的状态
int c = getState();
//若锁状态为0,表示没有线程获取锁
if (c == 0) {
//判断当前节点是否有前驱节点,若没有则CAS修改同步状态
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;
}
推荐阅读
-
JUC--ReenTrantLock学习(二)源码分析之获取锁
-
ReentrantLock详解
-
RxJava详解-线程切换原理篇
-
wget命令详解 博客分类: OS / linux wget
-
tar压缩解压缩命令详解 博客分类: Linux tar
-
GestureDetector使用详解
-
ORACLE数据泵 expdp/impdp使用详解(转) 博客分类: 数据库数据库 oracle 开发
-
ORACLE数据泵 expdp/impdp使用详解(转) 博客分类: 数据库数据库 oracle 开发
-
Android GestureDetector方法详解
-
详解G1 GC垃圾收集器 博客分类: JVM调优