AQS(AbstractQueuedSynchronizer)源码(一)

AQS(AbstractQueuedSynchronizer)源码(一)

AbstractQueuedSynchronizer是并发编程的核心框架
ReentrantLock. ReadWriteLock. CountDownLatch等都是基于他实现的

public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable

这是一个抽象类, 无法直接new出来 所以要继承并重写他的方法
需要重写的方法有:

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

AQS框架维护着两种模式, 独占模式和共享模式. 你可以任选其一实现,或者两者都实现
其中tryAcquiretryRelease是独占模式

tryAcquireSharedtryReleaseShared为共享模式

我们先看一下他的独占模式实现的机制
我们可以基于ReentrantLock讲解


独占模式

ReentrantLock的Lock方法(获取锁操作)

其中他以内部类的形式继承了AQS 也就是sync是一个AQS的子类,它辅助了ReentrantLock

public void lock() {
    sync.acquire(1);
}

接下来就要介绍AQS的acquire方法了

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

首先需要调用子类实现的tryAcquire方法(这里如果不实现就直接抛异常)
那接下来就要说一下AQS的实现机理了
AQS维护了一个int类型的变量state和一个队列(双向链表)

那ReentrantLock的公平锁是如何实现的呢?
ReentrantLock使用了独占模式,其state维护的是线程的数量值
同时ReentrantLock还保存了独占锁的线程
state一开始是0代表没有线程获取, state > 0 就代表有线程获取
state = 0的情况就让ReentrantLock保存的线程等于当前线程, 让state变为1
如果state > 0了 有其他线程抢夺资源. 注意:这里实现的是公平锁,
判断一下, 如果当前的线程和我保存的线程是一样的, 那么让state++
如果不一样的话就扔到等待队列去
队列是一种FIFO(先进先出)的数据结构, 他维护了线程的公平性,

好了回到代码

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire(arg) 的 arg 在上面的lock方法默认调用的是1 你就可以记住 arg是1
tryAcquire(arg)表示尝试获取锁, 如果获取成功返回true, 不成功返回false
因为判断的时候短路运算, 如果tryAcquire(arg)返回了true就不会执行后面的方法
否则返回false 代表当前线程没有拿到锁就应该放到等待队列中去, 同时判断一下有无响应中断机制

首先先来看一下ReentrantLock实现的tryAcquire(arg)方法, 但是这其实并不是必须的.因为这是他自己重写了实现的

protected final boolean tryAcquire(int acquires) {
	    //获取一下当前线程
        final Thread current = Thread.currentThread();
        //得到state就是上述维护的那个int值 这里是锁的当前数量(也就是可重入锁)
        int c = getState();
        //c=0的意思就是当前没有线程获得锁
        if (c == 0) {
            //如果等待队列中有元素,也就表示有人在前面排队
            //所以你新进来的线程必须等待
            //如果队列中没有元素了并且CAS操作状态成功(待会说)
            //setExclusiveOwnerThread这个方法就设置锁的线程绑定为当前线程
            //返回获取锁成功
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //否则判断一下当前线程和绑定的线程是否是同一个线程
        else if (current == getExclusiveOwnerThread()) {
        //这里只是把state值+1
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //如果程序运行到这 就说明有线程获得了锁, 并且不是当前线程
        //由于公平的原因就返回无法获得锁
        return false;
    }
CAS(CompareAndSwap)

CAS即比较并替换
一个经典问题i++这个问题在以前我们只是串行的就跑没有问题
但是在多线程情况下 可能出现脏读的情况
也就是内存的可见性和原子性的问题
i++这个操作看似一步语句, 其实包含了三步
第一步先拿到i的值
第二步 给i+1
第三步 再把i的值写回去
下列并发情况就会出现问题
在这里插入图片描述
第一个线程先读到内存中的值 i=1 并且给他+1
这个时候线程2先进来了,读了一下内存中的值, 这个时候线程1中还没有更改 所以线程2也读到的1
这个时候线程1, 2 都修改了i且都变成了2 但是我们的期望值应该是3
所以这个时候就用到了CAS技术
CAS有3个值 一个是内存值V(也就是期望值), 还有两个参数,A,B
CAS就是 我认为你的V应该等于A 如果相等 我把V替换成B 否则我什么也不做
所以上述的问题, 如果有两个线程获得执行权
在这里插入图片描述
且都进入到c == 0
线程1调用compareAndSetState(0, acquires) 这里的acquires是1
如果成功了就把state的0改成了1
这样线程2就算到了进入到了c==0 然后他调用compareAndSetState(0, acquires)方法
他期望内存值是0 可是,我们知道他已经被线程1改成了1 所以就什么也不做并返回false

好了之后的只要有CAS操作我都不用讲这么细了, 如果CAS失败就说明发生了并发问题

tryAcquire的方法还是比较好理解

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

接下来流程应该走到addWaiter(Node.EXCLUSIVE), arg)方法

private Node addWaiter(Node mode) {
    Node node = new Node(mode);
	//自旋添加结点
    for (;;) {
        Node oldTail = tail;
        //如果尾结点不是空的
        if (oldTail != null) {
            //因为新节点要添加到尾部
            //设置尾部的上一个结点为之前的尾部
            node.setPrevRelaxed(oldTail);
            //如果CAS操作失败了说明发生了并发问题
            //就自旋去设置重新跑一次循环
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                return node;
            }
           
        } else {//这里说明队列是空的,则初始化队列
            initializeSyncQueue();
        }
    }
}

private final void initializeSyncQueue() {
    Node h;
    if (HEAD.compareAndSet(this, null, (h = new Node())))
        tail = h;
}

接下来流程走到了acquireQueued方法
可以看到这里也用到了死循环自旋CAS操作, 这是一套标准组合拳
这个方法是尝试把线程挂起

final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
	            //这里的node是新添加的结点
                final Node p = node.predecessor();
                //如果新添加的是第二个结点
                //则有可能到这里的时候第一个运行完了,也就说没有线程获得锁了
                //所以这个线程再次尝试获取锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                //这里尝试线程是否安全挂起
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
	        //维护队列待会说
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }

先说维护队列
每个结点有几种状态,
这些状态由int类型来表示

static final int CANCELLED =  1;
static final int SIGNAL    = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;

CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。

SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。

CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。

这里我们先只关注SIGNAL
新创建的结点默认是为0的
也就是说 如果队列有4个 那么他的状态一定是 -1 -1 -1 0

过程走到了shouldParkAfterFailedAcquire方法

判断当前结点的前一个结点的等待状态是不是-1 如果是-1的话就可以安全的将这个挂起
如果前一个被取消了的话 需要维护队列 循环跳过所有被取消了的结点找到一个没有被取消的结点
把当前结点排到他的后面

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            
            return true;
        if (ws > 0) {
            //这里就是维护队列
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
           //尝试把当前线程设置为SIGNAL
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }
//通过LockSupport挂起线程,等待唤醒
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
}

ReentrantLock的unLock方法(释放锁操作)

释放锁比获取锁要简单一些
同样依靠的是AQS

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;
}

先将一下tryRelease尝试释放锁的方法

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //c是当前锁-1
            //判断一下当前线程如果和绑定的线程不是同一个抛异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //判断一下 锁-1的数量是不是等于0
            //如果成立就把绑定线程置空
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //不管如何都设置锁的数量-1
            setState(c);
            //如果是锁的数量变为0了也就是释放了返回true 否则只是把锁数量-1
            return free;
        }

然后再看一下release操作, 如果释放锁成功, 且队列不是空的情况下调用unparkSuccessor
其实这个猜也可以猜出来就是唤醒下一个线程

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

看一下唤醒下一个线程的方法

private void unparkSuccessor(Node node) {
       
        int ws = node.waitStatus;
        //这里先只说SINGAL
        //因为等待状态为SINGAL是-1表示挂起, 等待状态为0表示运行
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);
		
		//从队列里找出下一个需要唤醒的节点
        Node s = node.next;
        //如果这个结点是空或者他的状态标记为放弃
        //就从尾部向头搜索到第一个需要唤醒的结点
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

好了, 公平锁已经讲解完毕, 那么下面说一下非公平锁
如果你是认认真真看到这, 或者已经有了基础看到这 那么下面的应该不难理解
先看一下二者的差别

首先是公平锁

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;
    }

非公平锁

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;
        }

其实这里就发现 公平的调用了是否有等待队列, 而非公平的是直接尝试进行CAS获取
也就是说公平的只要有队列就返回获取锁失败
而非公平的则是抢占式的获取锁, 失败了再进队列
如果这个时候队列有3个线程在等待
这时候又新进来了一个线程, 在运行的线程刚好结束了, 那么新进的线程就抢到了锁, 这样就实现了非公平
注意: 非公平锁是默认的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值