独占锁ReentrantLock的原理
ReentrantLock是使用AQS来实现的,并且根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Sync类直接继承自AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平与公平策略。
abstract static class Sync extends AbstractQueuedSynchronizer {......}
static final class NonfairSync extends Sync {.......}
static final class FairSync extends Sync {........}
在这里,AQS的state状态值表示线程获取该锁的可重入次数,在默认情况下,state的值为0表示当前锁没有被任何线程持有。
当一个线程第一次获取该锁时会尝试使用CAS设置state的值为1,如果CAS成功则当前线程获取了该锁,然后记录该锁的持有者位当前线程。
在该线程没有释放锁的情况下第二次获取该锁,状态值被设置为2,这就是可重入次数。该线程释放锁时,会尝试使用CAS让状态值减1,如果减1后状态值为0,则当前线程释放该锁。
获取锁
1、void lock()
public void lock() {
sync.lock();
}
ReentrantLock的lock()委托给了sync类,根据创建ReentrantLock构造函数选择sync的实现是NonfairSync还是FairSync。
NonfairSync:非公平锁的实现
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
1、默认AQS的状态值为0,所以第一个调用Lock的线程会通过CAS设置状态值为1:compareAndSetState(0, 1)
2、CAS 成功后,setExclusiveOwnerThread
设置该锁持有者是当前线程
3、CAS 失败后,会调用AQS的acquire(1);
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ReentrantLock
重写了tryAcquire(arg)
:
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;
}
以上是非公平锁的实现代码。非公平是说先尝试获取锁的线程并不一定比后尝试获取锁的线程优先获取锁。
关于公平锁:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
}
实现公平性的核心代码:hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
1、如果当前线程有前驱节点 return true;
2、当前队列为空(h == t)return fasle;
3、h != t 并且 s == null,说明有一个元素将要作为AQS的第一个节点入队,return true;【参考前面AQS的入队过程】
4、h != t && s != null && s.thread != Thread.currentThread()); 说明队列里面的第一个元素不是当前线程 return true;
2、void lockInterruptibly()
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
该方法和lock()
方法类似,不同在于:它对中断进行响应。当前线程在调用该方法时,如果其他线程调用了当前线程的interrupt()
方法,则当前线程会抛出InterruptedException
异常,然后返回。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
3、boolean tryLock()
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁并返回true,否则返回false。
tryLock()
使用的是非公平策略。
注意:该方法不会引起当前线程阻塞。
释放锁
1、void unlock()
public void unlock() {
sync.release(1);
}
尝试释放锁
1、如果当前线程持有该锁,则调用该方法会让该线程对线程持有的AQS的状态值减1,如果减去1后当前状态值为0,则当前线程会释放该锁,否则仅仅是减1而已。
2、如果当前线程没有持有该锁而调用了该方法,则会抛出IllegalMonitorStateException
。
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;
}
小结:
- ReentrantLock的底层是使用AQS实现的可重入独占锁。
- AQS状态值为0表示当前锁空闲, >=1则说明该锁已经被占用。
- 该锁内部有公平与非公平实现,默认情况下是非公平的实现
- 该锁是独占锁,所以某时只有一个线程可以获取该锁