ReentrantLock
1. 介绍
1. 支持可重入:即可对同一资源重复加锁,当前线程获取该锁再次获取不会被阻塞。
2. 支持公平锁和非公平锁
2. 使用
ReentrantLock lock = new ReentrantLock();
public void test(){
lock.lock();
try {
// do something here
} catch (Exception e){
// ignore
} finally {
lock.unlock();
}
}
3. 源码
3.1 构造
// 无参构造,默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
构造就是创建了一个NonfairSync
对象。这个对象长啥样呢?
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
emm从命名上看,一个加锁,一个尝试获取锁。
3.2 lock()
这里看非公平锁哒!!!
public void lock() {
// 委托给sync去做;无参构造将sync指向了非公平锁
sync.lock();
}
final void lock() {
// CAS尝试将state设置为1
if (compareAndSetState(0, 1))
// 如果成功,就将当前线程设置为锁的持有者
setExclusiveOwnerThread(Thread.currentThread());
else
// 失败就
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// tryAcquire(arg)会走到这个方法
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,重入,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;
}
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;
}
// 否者, LockSupport.park()挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.3 unlock()
public void unlock() {
sync.release(1);
}
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; // state--
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果state已经为0了,释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);// 更新state
return free;
}
private void unparkSuccessor(Node node) {
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;
}
// unpark唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
总结:
ReentrantLock是基于AQS来实现的,里面维护了一个state变量,用来表示锁的状态。
在加锁的时候
- 首先通过CAS尝试将state从0改为1。
- 如果成功了,说明线程抢到了锁,把当前线程设置为锁的持有者;
- 如果失败了,判断当前锁的持有者是不是自己,是的话就重入,state++就好了。不是的话,重试之后仍然失败就LockSupport.park()挂起,封装成Node节点插入到队列中,等待唤醒。
解锁的过程是这样的
- 首先state–;
- 如果state已经减到0了,那么意味着线程释放锁了。之后换取头节点,通过LockSupport.unpark()唤醒下一个线程。那么下一个线程就会再之前park()的地方恢复,之后在尝试拿锁。
4. 怎么实现公平锁
公平锁在获取锁的时候,会先查看是否有前驱节点,如果有的话就放弃。其他的实现和非公平锁的一致的。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
5. 与synchronized的区别
ReentrantLock
基于AQS
实现;synchronized
基于监视器实现ReentrantLock
既可以是公平锁也可以是非公平锁;synchronized
是非公平锁ReentrantLock
要手动释放锁;synchronized
自动释放