本文介绍 ReentrantLock 及其实现原理,ReentrantLock和同步工具类的实现基础都是AQS
ReentrantLock 与 synchronize 的区别
- API层面,synchronize是jvm底层实现,ReentrantLock是java代码实现
- synchronize是关键字,ReentrantLock是java类
- 等待可中断,synchronized不可以中断,ReentrantLock可以设置超时等待避免死索
- synchronized的锁是非公平锁,ReentrantLock默认也是非公平锁,但可以设置成公平锁
- ReentrantLock可以同时绑定多个Condition对象,只需多次调用newCondition方法即可。
什么是AQS
- AQS即是AbstractQueuedSynchronizer,一个用来构建锁和同步工具的框架,包括常用的ReentrantLock、CountDownLatch、Semaphore等。
- AQS没有锁之类的概念,它有个state变量,是个int类型,在不同场合有着不同含义。本文研究的是锁,为了好理解,姑且先把state当成锁。
- AQS围绕state提供两种基本操作“获取”和“释放”,有条双向队列存放阻塞的等待线程,并提供一系列判断和处理方法,简单说几点:
- state是独占的,还是共享的;
- state被获取后,其他线程需要等待;
- state被释放后,唤醒等待线程;
- 线程等不及时,如何退出等待。
- 至于线程是否可以获得state,如何释放state,就不是AQS关心的了,要由子类具体实现。
ReentrantLock实现了独占功能
AbstractQueuedSynchronizer Node 的一些属性
| 属 性 | 定 义 | 举例使用地方 |
|---|---|---|
| Node SHARED = new Node() | 表示Node处于共享模式 | 在获取锁失败后会将线程放入队列中,这个时候要指定Node的模式 |
| Node EXCLUSIVE = null | 表示Node处于独占模式了 | 在获取锁失败后会将线程放入队列中,这个时候要指定Node的模式 |
ReentrantLock的使用
直接上代码,锁的使用简单来说就是加锁,执行自己的方法,释放锁
Lock lock = new ReentranLock();
lock.lock();
try{
//do something
}finally{
lock.unlock();
}
ReentrantLock 实现了公平锁和非公平锁,通过在构造方法中传入true或者false,它们两主要是在释放锁后对队列的唤醒操作不一样,公平锁是从队列头中唤醒,而非公平锁是所有都唤醒,然后来争抢资源。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
lock() 方法
公平锁:
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前的锁状态,如果为0则继续
int c = getState();
if (c == 0) {
// hasQueuedPredecessors 因为是公平锁所以需要判断当前队列是否有在等待的,
//如果队列中并等待,则调用compareAndSetState 加锁并设置当前线程为自己
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断获取锁的是不是当前线程本身,线程可以不停地lock来增加state的值,对应地需要unlock来解锁,直到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;
}
非公平锁:与公平锁主要的差异是公平锁会调用hasQueuedPredecessors
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前锁状态
int c = getState();
if (c == 0) {
// 直接尝试加锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断获取锁的是不是当前线程本身,线程可以不停地lock来增加state的值,对应地需要unlock来解锁,直到state为零。
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;
}
线程进入等待队列
当获取锁失败时,线程会进入队列中
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
* 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) {
// 创建节点,mode 是 Node.EXCLUSIVE 独占模式
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;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
enq是个死循环,保证Node一定能插入队列。注意到,当队列为空时,会先为头节点创建一个空的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;
}
}
}
}
执行完addWaiter 会执行 acquireQueued,
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 线程唤醒后尝试获取锁的过程。如果前一个节点正好是head,表示自己排在第一位,可以马上调用tryAcquire尝试。如果获取成功就简单了,直接修改自己为head。这步是实现公平锁的核心,保证释放锁时,由下个排队线程获取锁。
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);
}
}
释放锁 unlock()
里面调用的是release
public final boolean release(int arg) {
if (tryRelease(arg)) {
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;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
寻找下个待唤醒的线程是从队列尾向前查询的,找到线程后调用LockSupport的unpark方法唤醒线程。被唤醒的线程重新执行acquireQueued里的循环,上面有提到acquireQueued
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* 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);
/*
* 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);
}
本文详细介绍了ReentrantLock的工作原理及其与synchronized关键字的区别。ReentrantLock基于AQS框架实现,支持公平锁与非公平锁,能够灵活控制锁的获取与释放行为。
1287

被折叠的 条评论
为什么被折叠?



