AbstractQueueSynchronizer-共享式获取或释放

一、共享式锁实现及应用

上一篇文章分享了独占式获或释放同步状态的过程。本文分享AQS共享式获取或释放同步状态,共享式获取或释放过程与独占式获取或释放过程大体相同,按照相同流程进行分析。首先,创建一个读锁类,用于对资源的共享式读访问,其核心是创建继承自AQS的内部类,并重写tryAcquireShared和tryReleaseShared方法。读锁类实现锁接口,并创建同步器成员变量,为简便起见主要实现了Lock接口中的lock和unlock方法。

读锁实现:

public class TestSharedReadLock implements Lock {
    private MySync sync;
    private static Integer permits;
    public TestSharedReadLock(Integer permits) {
        this.permits = permits;
        sync = new MySync(permits);
    }
    public static class MySync extends AbstractQueuedSynchronizer {
        public MySync(int permits) {
            setState(permits);
        }
        @Override
        protected int tryAcquireShared(int arg) {
            for(;;) {
                int curPermits = getState();
                int postPermits = curPermits - arg;
                if(postPermits < 0 || compareAndSetState(curPermits, postPermits)) {
                    return postPermits;
                }
            }
        }
        @Override
        protected boolean tryReleaseShared(int arg) {
            for(;;) {
                int curPermits = getState();
                if(curPermits == permits) {
                    throw new IllegalMonitorStateException();
                }
                int postPermits = curPermits + arg;
                if(compareAndSetState(curPermits, postPermits)) {
                    return true;
                }
            }
        }
    }
    @Override
    public void lock() {
        sync.acquireShared(1);
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {

    }
    @Override
    public boolean tryLock() {
        return false;
    }
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }
    @Override
    public void unlock() {
        sync.releaseShared(1);
    }
    @Override
    public Condition newCondition() {
        return null;
    }
}

对该读锁做简单测试,将其应用到如下场景中:

public class TestReadLockCase {
    private static TestSharedReadLock readLock = new TestSharedReadLock(3);
    public static class Task implements Runnable {

        @Override
        public void run() {
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + ": 进入lock");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
            } finally {
            }
            readLock.unlock();
            System.out.println(Thread.currentThread().getName() + ": 释放lock");
        }
    }
    public static void main(String ... args) {
        Instant startInstant = Instant.now();
        List<Thread> threads = new ArrayList<>();
        for(int i=0; i< 10; i++) {
            threads.add(new Thread(new Task(), "Thread-" + i));
        }
        threads.stream().forEach(t -> {
            t.start();
        });
        threads.stream().forEach(t -> {
            try {
                t.join();
            } catch (Exception e) {
            }
        });
        System.out.println("cost = " + Duration.between(startInstant, Instant.now()).toMillis() + " ms");
    }
}

输出结果:
Thread-0: 进入lock
Thread-2: 进入lock
Thread-1: 进入lock
Thread-2: 释放lock
Thread-3: 进入lock
Thread-0: 释放lock
Thread-1: 释放lock
Thread-5: 进入lock
Thread-4: 进入lock
Thread-3: 释放lock
Thread-4: 释放lock
Thread-7: 进入lock
Thread-6: 进入lock
Thread-5: 释放lock
Thread-8: 进入lock
Thread-7: 释放lock
Thread-6: 释放lock
Thread-8: 释放lock
Thread-9: 进入lock
Thread-9: 释放lock
cost = 8094 ms

以上场景模拟了共享式获取读锁的场景,同一时间最多允许3个线程获取锁作业,其它线程阻塞等待,模拟场景中开启了10个线程获取锁,模拟任务处理耗时2000ms,每批3个执行线程,分4批完成,耗时8094ms。下文借这个应用示例对共享式获取锁和释放锁的逻辑进行分析。

二、共享式获取

在示例中,读锁的lock逻辑调用了AQS中共享式获取的模板方法是acquireShared,方法源码如下:

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

主要逻辑:

  • a. tryAcquireShared,调用用户实现的tryAcquireShared方法,返回同步状态>=0表示当前线程获取同步状态成功;返回<0表示获取同步状态失败
  • b. doAcquireShared,(addWaiter)如果当前线程获取同步状态失败,则将当前线程封装成节点添加到双向链表尾部;如果当前节点的前驱节点是头节点,则再次尝试共享式获取同步状态;如果再次获取失败,则判断是否将当前节点挂起;将节点挂起并检查中断状态;

acquireShared方法首先调用我们自定义的tryAcquireShared方法:

        protected int tryAcquireShared(int arg) {
            for(;;) {
                int curPermits = getState();
                int postPermits = curPermits - arg;
                if(postPermits < 0 || compareAndSetState(curPermits, postPermits)) {
                    return postPermits;
                }
            }
        }

改方法返回当前同步状态的值,通过无限循和CAS保证在并发情况下修改同步状态值成功,如果返回同步状态>=0则表示获取同步状态成功,如果返回同步状态<0则表示获取同步状态失败。

获取同步状态失败后,调用doAcquireShared方法,该方法与独占是获取逻辑中acquireQueued的处理过程类似,只是将addWaiter方法也封装在了方法体中。doAcquireShared方法描述如下:

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

其中addWaiter方法逻辑与独占式获取相同,不再赘述。doAcquireShared方法中剩余逻辑也基本和acquireQueued方法一致,只是调用获取同步状态方法及后续处理逻辑稍有不同,doAcquireShared方法中调用共享式获取tryAcquireShared方法,acquireQueued中调用独占式获取tryAcquire方法,另外,acquireQueued方法中如果获取同步状态成功则将当前节点设置为头节点,doAcquireShared方法中如果获取同步状态成功调用setHeadAndPropagate方法,其逻辑如下。shouldParkAfterFailedAcquire、parkAndCheckInterrupt等方法分析见上一篇文章。

setHeadAndPropagate方法描述如下:

        Node h = head; // Record old head for check below
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

主要逻辑:

  • a. setHead,将节点设置为头节点
  • b. 如果剩余同步状态(资源)>0,或头结点的状态<0(SIGNAL、PROPAGATE),则判断其后继节点,如果后继节点为空或SHARED(SHARED状态通过共享类的静态变量实现),则继续通过doReleaseShared唤醒头节点的后继节点

doReleaseShared方法描述如下:

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

如果头节点的状态为SIGNAL,则将头结点状态设置为0并唤醒后继节点,然后再将节点状态设置为PROPAGATE,便于其它竞争线程通过setHeadAndPropagate方法和doReleaseShared将PROPAGATE状态向后传播。注意,当有多个节点释放状态或被唤醒的后继节点竞争成功时,该方法存在并发情况,如果在修改头结点状态的过程中头结点被其它节点重新设置,则当前线程继续循环设置新头结点的状态。

三、共享式释放

在示例中,读锁的unlock逻辑调用了AQS中独占式获取的模板方法是releaseShared,方法源码如下:

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

主要逻辑:

  • a. tryReleaseShared,调用用户实现的tryReleaseShared方法释放同步状态,返回成功表示释放同步状态成功
  • b. doReleaseShared,唤醒后继节点并将状态设置为PROPAGATE

releaseShared方法首先调用用户实现的tryReleaseShared方法,如下:

        protected boolean tryReleaseShared(int arg) {
            for(;;) {
                int curPermits = getState();
                if(curPermits == permits) {
                    throw new IllegalMonitorStateException();
                }
                int postPermits = curPermits + arg;
                if(compareAndSetState(curPermits, postPermits)) {
                    return true;
                }
            }
        }

并发情况下,同步状态可能已经被修改,因此tryReleaseShared方法中如果当前资源等于准入资源,则抛出异常,否则通过无限循和CAS保证在并发情况下修改同步状态值成功。

tryReleaseShared方法前面已经分析过,不在赘述。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值