源码分析:同步基础框架——AbstractQueuedSynchronizer(AQS) (2)

三、定义锁和解锁方法

public class MyAQSLock{ private final Sync sync; MyAQSLock(){ sync = new Sync(); } class Sync extends AbstractQueuedSynchronizer{ ... } public void lock(){ // 调用同步器,获得锁 sync.acquire(1); } public boolean tryLock(){ // 尝试获得锁,如果没有获取到锁,则立即返回false return sync.tryAcquire(1); } public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException{ // 尝试获得锁,如果没有获取到锁,允许等待一段时间 return sync.tryAcquireNanos(1,unit.toNanos(timeout)); } public void unLock(){ // 解锁 sync.release(1); } public boolean isLocked(){ // 判断锁是否已经被占用 return sync.isHeldExclusively(); } }

四、测试我们的锁

static int count = 0; public static void main(String[] args) throws InterruptedException{ MyAQSLock myAQSLock = new MyAQSLock(); CountDownLatch countDownLatch = new CountDownLatch(1000); IntStream.range(0,1000).forEach(i->new Thread(()->{ myAQSLock.lock(); try{ IntStream.range(0,10000).forEach(j->{ count++; }); }finally{ myAQSLock.unLock(); } countDownLatch.countDown(); }).start()); countDownLatch.await(); System.out.println(count); }

最后正确输出10000000,说明我们实现的锁是有效的。但是要注意我们自己写的这个锁是不支持重入的。

代码实现分析 获得锁:public void lock()

lock()方法会调用sync.acquire(int)方法,acquire在AQS里面,方法被final修饰,作为基础框架逻辑部分,不允许被继承,源码展示:

public final void acquire(int arg) { // tryAcquire 是我们自己实现的方法,具体实现看上面 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // acquireQueued 返回true表示线程被中断了,中断当前线程 selfInterrupt(); }

上面acquire()主要的逻辑有:

尝试获得锁,调用tryAcquire(arg)方法,该方法的逻辑在我们自定义的MyAQSLock类中,我们利用了compareAndSetState来保证state字段的原子性。

如果tryAcquire返回true的话,if分支会直接退出,表示成功获得锁,继续执行调用lock() 方法后面的逻辑;

如果tryAcquire返回false的话,表示没有获得锁,会继续执行 && 后面的逻辑;

首先会调用addWaiter(Node.EXCLUSIVE)方法为当前线程创建排队节点,并加入到队列,Node.EXCLUSIVE代表这个节点是独占排他锁的意思,具体源码如下:

private Node addWaiter(Node mode) { // 为当前线程创建一个节点,最后会返回出去这个节点 Node node = new Node(Thread.currentThread(), mode); // 队列不为空时,快速尝试在同步队列尾部添加当前节点,如果失败了会进入enq方法自旋入队 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 上面入队失败了,或者是pred为空(第一个排队的线程进来),继续自旋入队 enq(node); return node; } private Node enq(final Node node) { // 空的for循环,自旋操作,直到成功把节点加入到同步队列 for (;;) { // 同步队列尾巴 Node t = tail; if (t == null) { // Must initialize // 尾巴是空的,还没有初始化, 第一个排队的线程进来的话,队头队尾都是同一个节点 if (compareAndSetHead(new Node())) tail = head; } else { // 进入到这里,说明同步队列已经有线程在排队了 // 当前节点前驱直接指向同步队里的尾节点 node.prev = t; // CAS 修改尾节点为当前节点 if (compareAndSetTail(t, node)) { // t还是老的尾节点,修改新的尾节点后老的尾节点的下一个节点就是当前节点,建立他们的联系 t.next = node; // 成功把当前节点加入到了同步队列,返回当前节点,退出自旋 return t; } } } }

addWaiter()方法总结:首先会快速尝试一次在队列的尾部添加当前线程节点,如果失败的话(在这个时候,可能有新的线程也没有获得锁,并且跑在当前的前面加入到同步队列了),会调用enq逻辑进行自旋加入队尾,直到成功加入队列为止。

再次尝试从同步队列获得锁acquireQueued(node,arg)

final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋操作 for (;;) { // 当前节点的上一个节点 final Node p = node.predecessor(); //**①** // 如果前驱节点是头结点,然后去尝试获得锁,tryAcquire是我们自己实现的获得锁逻辑 **if (p == head && tryAcquire(arg)) { //②** // 当前线程成功获得锁,当前节点设置为头结点 setHead(node); p.next = null; // help GC failed = false; // 返回false,表示没有被中断 return interrupted; } // 到这里说明p != head 或者 **tryAcquire** 返回了false,还是没获得锁,这时候就需要阻塞线程了 // shouldParkAfterFailedAcquire 如果线程应阻塞,则返回true // parkAndCheckInterrupt 阻塞当前线程 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { // 节点被取消了 if (failed) cancelAcquire(node); } } private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // SIGNAL值为-1,表示pred节点的后继节点包含的线程需要运行,也就是unpark if (ws == Node.SIGNAL) return true; if (ws > 0) { // 大于0的值只有1, 1表示线程被取消 // 进入到这里说明 pred 节点被取消了,需要从同步队列上删掉它 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 一般初始时为0,设置成-1 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } // 返回false,外面会一直自旋操作 return false; } private final boolean parkAndCheckInterrupt() { // 调用底层的Unsafe一直阻塞线程 LockSupport.park(this); // 被unpark唤醒之后,会继续回去自旋获得锁,并返回线程在此期间是否有被中断 return Thread.interrupted(); }

lock方法总结:

尝试获得锁,方法tryAcquire:

成功获得锁,直接退出;没有获得锁,继续执行;

新建排队节点,并加入到同步队列,方法addWaiter:

队列不为空时,尝试一次快速直接把节点加入到队列尾巴上;如果队尾为空或者快速添加失败,继续执行下面逻辑

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssyjz.html