模板设计模式_浅谈AQS与模板设计模式

 

在Java并发体系中,同步器是绕不开的话题。在顶层的API里无论是ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等等这些用于线程同步互斥的工具,内部都是用AbstractQueuedSynchronizer(AQS同步器)做为控制。本篇主要介绍如何基于AQS自定义一个锁

第一步:定义MyLock实现Lock

Doug Lea大神在JDK1.5编写了一个Lock接口,里面定义了实现一个锁的基本方法,我们只需编写一个MyLock类实现这个接口就好

class MyLock implements Lock {
    /**
     * 加锁。如果不成功则进入等待队列
     */
    @Override
    public void lock() {}
    /**
    * 加锁(可被interrupt)
    */
    @Override
    public void lockInterruptibly() throws InterruptedException {}
    /**
     * 尝试加锁
     */
    @Override
    public boolean tryLock() {}
    /**
     * 加锁 带超时的
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {}
    /**
    * 释放锁
    */
    @Override
    public void unlock() {}
    /**
    * 返回一个条件变量(不在本案例谈论)
    */
    @Override
    public Condition newCondition() {}
}

定义好MyLock后,接下来就是实现各个方法的逻辑,达到真正的用于线程间同步互斥的需求。

第二步:自定义一个MySync继承自AQS

接下来我们需要自定义一个继承自AQS的MySync同步器。实现自定义的MySync前,先了解AQS内部的一些基本概念。在AQS中主要的一些成员属性如下:

c2bdef63e7b74eb9fc0b924ff9a8dad8.png

  • state:用于标记资源状态,如果为0表示资源没有被占用,可以加锁成功。如果大于0表示资源已经被占用,然后根据自己的定义去实现是否允许对共享资源进行操作。比如ReentrantLock的实现方式是当state大于0,那么表示已经有线程获得锁了,我们都知道ReentrantLock是可重入的,其原理就是当有线程次进入同一个lock标记的临界区时,先判断这个线程是否是获得锁的那个线程,如果是,state会+1,此时state会等于2。当unlock时,会一层一层的减1.直到state等于0则表示完全释放锁成功。

  • head、tail:用于存放获得锁失败的线程。在AQS中,每一个线程会被封装成一个Node节点,这些节点如果获得锁资源失败会链在head、tail中,成为一个双向链表结构

  • exclusiveOwnerThread:用于存放当前获得锁的线程,正如在state说明的那样。ReentrantLock判断可重入的条件就是用这个exclusiveOwnerThread线程跟申请获得锁的线程做比较,如果是同一个线程,则state+1,并重入加锁成功。

知道这些概念后我们就可以自定义一个AQS同步器:

public final class MySync extends AbstractQueuedSynchronizer {
    /**
    * 尝试加锁
    */
    @Override
    protected boolean tryAcquire(int arg) {
        if (compareAndSetState(0, 1)) {
            // 修改state状态成功后设置当前线程为占有锁资源线程
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    /**
    * 释放锁
    */
    @Override
    protected boolean tryRelease(int arg) {
        setExclusiveOwnerThread(null);
        // state有volatile修饰,为了保证解锁后其他的一些变量对其他线程可见,把setExclusiveOwnerThread(null)放到上面 happens-before中定义的 volatile规则
        setState(0);
        return true;
    }
    /**
    * 判断是否是独占锁
    */
    @Override
    protected boolean isHeldExclusively() {
        return getState() == 1;
    }
}

第三步:将MySync组合进MyLock

最后一步就是将第一步中的所有方法逻辑完成

class MyLock implements Lock {

    // 组合自定义同步器
    private MySync sync = new MySync();

    /**
     * 加锁。如果不成功则进入等待队列
     */
    @Override
    public void lock() {
        sync.acquire(1);
    }
    /**
    * 加锁(可被interrupt)
    */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    /**
     * 尝试加锁
     */
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    /**
     * 加锁 带超时的
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toMillis(time));
    }
    /**
    * 释放锁
    */
    @Override
    public void unlock() {
        sync.release(0);
    }
    /**
    * 返回一个条件变量(不在本案例谈论)
    */
    @Override
    public Condition newCondition() {
        return null;
    }
}

完成整个MyLock的逻辑后,我们发现在lock()、unlock()中调用的自定义同步器的方法.acquire()和.release()方法。我们就以在lock()方法中调用.acquire()方法说明模板设计模式在AQS中的应用。

点进.acquire()方法后,发现改该方法是来自AbstractQueuedSynchronizer中:

a9f56fbfe67aed233fa7cdb9e1897bd4.png

在这里面可以看到 tryAcquire方法,继续点进去看看tryAcquire(),发现该方法是一个必须被重写的方法,否则抛出一个运行时异常。模板方法设计模式在这里得以体现,再回到我们第二部中自定义的MySync中,就是重写了AQS中的tryAcquire()方法。

00e7902087d4f82f6d3b661e3fadea6a.png

因此整个自定义加锁的流程如下:

调用MyLock的lock(),lock()方法调用AQS的acquire()方法,在acquire()方法中调用了tryAcquire()方法进行加锁。而tryAcquire()方法在AQS中是一个必须让子类自定义重写的方法,否则会抛出一个异常。因此调用tryAcquire()时实际上是调用了我们自定义的MySync类中tryAcquire()方法

总结

AQS同步器作为Java并发体系下的关键类,在各种并发工具中都有它的身影,如ReentrantLock、Semaphore等。这些并发工具用于控制同步互斥的手段都是采用AQS,外加Cas机制。AQS采用了模板方法设计模式让子类们自定义同步互斥的条件,比如本案例中MySync类重写了tryAcquire方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值