鼠年新的工作日开始了,新的一年新的工作学习,第一个工作日给自己定一些要求吧。
想写一个系列【隐匿撕源码】 剖析各种经典框架,工具,JDK的源码提升自己,同时给大家分享一些心得。这篇文章就作为开端,干就完事了。
ReentrantLock 是JDK给我们提供的显示锁 在功能上远远强于synchronized
是功能上 不是性能上,随着JDK版本的一代又一代升级 synchronized的性能已经远远不是以前的重量级锁那么沉重
我们来看ReentrantLock的构造方法 有两种 传入boolean值 选择此锁是否是公平锁
默认是非公平锁,性能高 可以插队
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
创建了公平sync或者非公平sync之后 赋值给了一个变量 我们看一下这个Sync到底是何方神圣
源码如图
看到这里我们发现Sync是一个抽象静态内部类 他继承了一个类叫AbstractQueuedSynchronizer 这也就是我们标题的AQS
先不管AQS是什么 看一下这个类的继承结构
所以我们得出一个结论 RenntrantLock的所有锁行为都是基于这个Sync实现的 而这个Sync又是根据AQS实现的,那我们就真正进入AQS中探索
先做总结,对AQS有一个清晰的认识:
AQS是一个同步队列,是一个双向链表。
维护了这样一个同步队列
他使用了模板方法设计模式 提供了大量模板方法
分为两种 一种是模板 一种是需要子类覆盖的流程方法
如果不懂模板方法的小伙伴需要补充一些设计模式的知识
下面我分类 一一列举AQS中模板方法的源码并附上注释
//获取同步状态()
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//获取同步状态 (获取同步状态的时候可以响应中断)
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
// 响应中断的同时具有等待超时功能
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
//独占释放锁
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
共享式
//共享获取同步状态
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//共享获取同步状态(可以响应中断)
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) = 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
//共享释放
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
流程方法(需要AQS子类去覆盖的)
//独占式获取锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
//共享获取
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
//独占释放
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//共享释放
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
//表示此同步器是否处于独占模式
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
提一下final 的状态方法
//这是同步状态标志位
private volatile int state;
//因为是volatile修饰的 所以不需要考虑获取的锁
protected final int getState() {
return state;
}
//这是有原子性隐患的set方法
protected final void setState(int newState) {
state = newState;
}
//CAS原子操作的set方法
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
唔 如果不知道volatile和CAS 的需要补充一些多线程知识 后期我会加入这块博客 暂时木有
其实AQS就是这么多模板方法 有了这些方法就可以随心所欲的实现自己想要的功能的锁了
调用关系如图
我们知道了AQS的模式 ,那么这次再看ReentrantLock里面的Sync,NonfairSync,FairSync是如何实现锁的就容易很多
Sync: abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果标记为是0 那么设置标记位 同时把当前线程记录
//因为是可重入锁 所以只需要记录线程就可以
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果标记为不是0 那么说明已经有人拿到了锁 判断拿到锁的是否是当前线程
//如果是 记录重入次数
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
//这里是重入次数不能大于 int最大值 。。
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 如果标记为不是0 而且拿到锁的不是当前线程 则获取锁失败
return false;
}
//释放锁
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//如果当前线程不是拿到锁的线程 则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果计算之后是0 说明可以释放锁 设置当前线程为null 即可
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c); 设置state
return free;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
//Condition类
final ConditionObject newCondition() {
return new ConditionObject();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
}
NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//如果是未上锁状态(0) 则更改成1 并写入当前线程
//这段代码公平锁是没有的 因为非公平锁是可以插队
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
//如果不是 cas失败 那就尝试获取锁
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
//Sync源码中有
return nonfairTryAcquire(acquires);
}
}
FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
//公平锁上锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果当前锁没上锁
if (c == 0) {
//hasQueuedPredecessors 这个方法确定当前线程必须是节点的头节点
//实现了公平锁
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;
}
}
这样ReentrantLock的公平锁和非公平锁大致思路已经说完了
源码中的思路我都用注释的方式写在代码中了
接下来我们仿照Lock实现一个 可以同时有两个线程访问的锁:TwinsLock
public class TwinsLock implements Lock{
private static final class Sync extends AbstractQueuedSynchronizer{
Sync(int count){
if (count <= 0) {
throw new IllegalArgumentException("count must large zere");
}
setState(count);
}
@Override
protected int tryAcquireShared(int reduceCount) {
for(;;){
int current = getState();
System.out.println(Thread.currentThread().getName()+"尝试获取锁");
int newCount = current - reduceCount;
if (newCount<0 || compareAndSetState(current,newCount)) {
String info = newCount 0;
}
//忽略 不是重点
@Override
public boolean tryLock(long time, TimeUnit unit)
throws InterruptedException {
return false;
}
//忽略 不是重点
@Override
public Condition newCondition() {
return null;
}
public static void main(String[] args) throws InterruptedException {
final Lock lock = new TwinsLock();
class Worker extends Thread{
@Override
public void run() {
while(true){
lock.lock();
System.out.println("["+Thread.currentThread().getName()+"]开始执行============");
try{
Thread.sleep(1000);
String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("hh:mm:ss"));
System.out.println(now+" 线程["+Thread.currentThread().getName()+"]正在等待");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
}
//启动10个线程
for (int i = 0; i < 10; i++) {
Worker w = new Worker();
w.start();
}
}
}
执行结果:
AQS使用一个同步队列 使用FIFO 先入先出
我们可以基于AQS实现各种各样的花哨功能的锁
其中ReentrantLock就是我们常用的
而且在使用过程中我们完全感受不到AQS的存在
是一个非常棒的设计
并发工具 如果闭锁 CyclicBarrier Semaphore等等都是基于AQS实现的
tip:其中CyclicBarrier依赖的是ReentrantLock 所以也算是依赖AQS