如何实现一把锁:
-
如何表示锁的状态:有锁、无锁
boolean state;// true 有锁 false 无锁 只能表示两种状态
为了实现锁重入,那么我们需要记录锁重入次数
int times;
两个变量有点冗余了,所以我们直接用 int state;来表示锁状态和重入次数:
0无锁,大于0重入次数 特别地:1为重入一次,也即只加锁一次 -
如何保证多线程抢锁线程安全? CAS
-
如何处理获取不到锁的线程?
自旋 阻塞.自旋+阻塞 -
如何释放锁? 自旋:自己抢锁
阻塞:唤醒
自旋锁缺点:CPU 占用不干事,导致性能障碍,简称占着茅坑不拉屎
自旋锁优点:适用于执行步骤较少且快的操作,自旋一会儿马上就能获取到锁,这样不会消耗太多CPU资源
便用场景:争用较少且代码量小的 临界区
什么是AQS
AQS核心源码
//获取锁代码
public final void acquire(int arg) {
if (!tryAcquire(arg) && //子类判定获取锁失败
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取锁失败后添加到阻塞队列
selfInterrupt();
}
//子类实现获取锁逻辑,AQS不知道你怎么用state来上锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//释放锁代码
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 boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
一句话总结:
子类只需要实现自己的获取锁逻辑和释放锁逻辑即可,至于排队阻塞等待、唤醒机制均由AQS来完成。
ReentrantLock 原理
概念
基于AQS实现的可重入锁
核心变量和构造器
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
//默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
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 tryRelease(int releases) {
int c = getState() - releases;
// 当前线程不是上锁的那个线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //不是重入锁,那么当前线程一定是释放锁了,然后我们把当前AQS用于保存当前锁对象的变量ExclusiveOwnerThread设置为空,表明释放锁成功
free = true;
setExclusiveOwnerThread(null);
}
//注意:此时state全局变量没有改变,也就意味着在setState之前没有别的线程能够获取锁,保证了以上操作的原子性
setState(c);
//如果返回true,就是告诉AQS我当前释放锁成功了,可以去唤醒正在等待锁的线程了
return free;
}
final ConditionObject newCondition() {
return new ConditionObject();
}
}
static final class NonfairSync extends Sync {
//由ReentrantLock调用获取锁
final void lock() {
//非公平锁,直接抢锁,不管有没有线程排队
if (compareAndSetState(0, 1))
//上锁成功,标识当前线程为获取锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
//抢锁失败,进去AQS标准的获取锁流程
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
//由ReentrantLock调用获取锁
final void lock() {
//直接进入AQS标准的获取锁流程
acquire(1);
}
//AQS调用,子类自己实现获取锁的流程
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//此时正好持有锁的线程释放了锁
if (c == 0) {
if (!hasQueuedPredecessors() && //这里和非公平锁的区别在于,hasQueuedPredecessors会看看队列中是否有线程在排队,没有的话在通过CAS抢锁
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;
}
//返回false表示需要AQS来将当前线程放入阻塞队列,然后进行阻塞操作等待唤醒获取锁
return false;
}
}