java中ReentrantLock实现,公平锁和非公平锁,AQS并发队列,
Posted Leo Han
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中ReentrantLock实现,公平锁和非公平锁,AQS并发队列,相关的知识,希望对你有一定的参考价值。
一般在java中,遇到并发的时候,我们很多时候可能会使用synchronized关键字来实现锁,但是synchronized关键字有一定的缺陷(比如无法实现类似读锁、非公平),而Lock可以实现。在java中常用的有ReentrantLock,我们看下实现,一般我们在代码中如下方式来调用锁:
ReentrantLock lock = new ReentrantLock();
lock.lock();
xxxxxx
lock.unlock();
这里默认的是非公平锁,可以在构造ReentrantLock
的时候,传入,指定是公平锁还是非公平锁:
public ReentrantLock()
sync = new NonfairSync();
public ReentrantLock(boolean fair)
sync = fair ? new FairSync() : new NonfairSync();
我们来看看其实现机制。
在说这个之前,我们需要了解一个类AbstractQueuedSynchronizer
,就是常说的并发工具 AQS,java中并发类很多都是基于AQS实现的。
这里说下AQS中几个比较重要的变量:
// 请求链表头
private transient volatile Node head;
// 请求链表尾
private transient volatile Node tail;
// 当前锁被请求次数,为0时表示当前锁空闲 ,实现了可重入(通过state记录锁被请求多少次,只有当state=0的时候,表示锁被释放了)
private volatile int state;
// 这个是父类AbstractOwnableSynchronizer的变量,用来记录当前获取到锁的线程
private transient Thread exclusiveOwnerThread;
当我们调用lock.lock
的时候:
public void lock()
sync.lock();
而这里的sync则由两种实现,公平锁和非公平锁,我们先看看公平锁实现:
// FairSync
final void lock()
acquire(1);
public final void acquire(int arg)
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
这里通过acquire
获取资源量,每次请求都是1,首先进行tryAcquire
,如果tryAcquire
失败,那么acquireQueued
添加到请求链表中,如果需要中断,那么会中断当前线程。
我们首先看看tryAcquire
:
protected final boolean tryAcquire(int acquires)
final Thread current = Thread.currentThread();
int c = getState();
// c=0 表示当前锁空闲
if (c == 0)
// hasQueuedPredecessors判断当前是否有请求等待队列
// compareAndSetState如果没有等待队列,利用cas将state设置为当前请求量
// 如果cas设置成功,将exclusiveOwnerThread设置为当前线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires))
setExclusiveOwnerThread(current);
return true;
// 如果getExclusiveOwnerThread为当前线程,表示当前线程已经获取到锁了,可重入,调整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;
可以看到,tryAcquire 方法首先会判断锁的空闲状态,如果锁空闲,通过cas来获取锁,如果锁不空闲,判断是否是当前线程获取到了锁,如果是的话,那么更新state状态,否则返回false
如果tryAcquire
返回false,那么会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
逻辑:
private Node addWaiter(Node mode)
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null)
node.prev = pred;
if (compareAndSetTail(pred, node))
pred.next = node;
return node;
enq(node);
return node;
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;
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);
addWaiter
主要是通过CAS将请求添加到请求链表的尾部,需要注意的是,传入的mode
,·Node.EXCLUSIVE表示独占锁, Node.SHARED共享锁·然后在acquireQueued
中,会for死循环来获取锁。而shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()
则通过LockSupport.park
来阻塞当前线程(这里是通过 LockSupport.park(this);来阻塞当前线程,必须等其他线程调用 LockSupport.unpark(hread);才能被唤醒
)。
到这里公平锁获取锁的过程就完了,我们看下怎么释放锁的。
public void unlock()
sync.release(1);
public final boolean release(int arg)
if (tryRelease(arg))
// 如果锁被释放 空闲,并且请求链表不为空,那么通过`LockSupport.unpark`唤醒请求链表头的线程。
Node h = head;
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;
// c==0表示锁空闲,锁已经释放,重置exclusiveOwnerThread 为null
if (c == 0)
free = true;
setExclusiveOwnerThread(null);
setState(c);
return free;
可以看到,释放锁的时候,主要是调整了state状态值以及exclusiveOwnerThread
,以及如果锁被释放空闲之后,会唤醒请求链表头部的请求来获取锁.
// NonfairSync
final void lock()
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
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;
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;
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;
可以看到,获取锁的时候,对于非公平锁,上来就是直接获取锁,在获取不到锁的情况下,也是走acquire
,但是也公平锁不同的是,非公平锁tryAcquire
的时候,并不会关心当前请求队列是否有请求,而是直接通过cas并发获取锁。
这里我们总结下ReentrantLock公平锁和非公平锁的实现逻辑:
公平锁获取锁
1. 如果锁空闲,那么只有当请求链表为空的时候才会去获取锁,获取锁的时候通过cas设置state,并将exclusiveOwnerThread设置为当前线程
2. 如果当前锁的持有线程是当前线程,那么可以直接调整锁的持有量,将state设置为state+acquires,通过cas更新state
3. 如果上面不满足,那么会将当前请求放入到请求队列中 ,并通过LockSupport.park来阻塞请求线程
公平锁释放锁
1 首先通过cas将state线程持有量-acquires
2. 如果state==0,表名当前线程没有地方还需要锁,锁已经全部释放,锁空闲,设置exclusiveOwnerThread为空
3. 如果锁空闲(第二步成功),那么会通过LockSupport.unpark唤醒请求链表的头部线程
非公平锁获取锁
1. 公平锁首先是直接通过cas设置state状态,如果成功,表示获取锁成功(非公平锁不会去判断锁空闲的时候请求链表有没有请求)
2. 如果请求锁失败,那么通过`nonfairTryAcquire`去再次请求,在请求的时候如果锁空闲,不会判断请求链表有没有请求而是直接通过cas获取锁
3. 如果第二步失败,也公平锁一样,加入到请求链表中
非公平锁释放锁
非公平锁释放锁与公平锁一样,没有区别
在ReentrantLock中,获取锁的时候是通过cas设置state变量来实现的,state变量既用来实现锁的获取和释放,又结合exclusiveOwnerThread来实现可重入锁。
当state == 0的时候,表示锁空闲
,获取锁通过cas设置state ,如果设置成功,则获取锁成功,结合exclusiveOwnerThread
,如果exclusiveOwnerThread和当前线程相等,这表示当前线程已经获取到锁了,这时候在获取的话,给state增加accquires,当释放锁的时候,直接通过cas设置state为state - accquires,如果state == 0 则表明当前线程持有的锁变量全部释放完,锁空闲。
当线程获取不到锁的时候,通过LockSupport.park来阻塞线程进行等待,通过LockSupport.unpark来唤醒线程重新请求获取锁
以上是关于java中ReentrantLock实现,公平锁和非公平锁,AQS并发队列,的主要内容,如果未能解决你的问题,请参考以下文章
java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁
JAVA进阶之路-ReentrantLock的公平锁和非公平锁