理解设计模式—模板方法与AQS

定义:封装一个模板结构,将具体内容延迟到子类去实现

将多个类共有的方法和功能抽取出来,封装到抽象类,对于这些公有方法(模板方法)使用final修饰,需要通过子类扩张的定义成抽象(abstract)方法,有子类实现其自有特性。

JDK AQS 抽象队列同步器就是一个构建锁和同步器的模板,使用它可以构建ReentrantLock(独占型),CountDownLacth(共享型),Semaphore(共享型)等同步组件。

AQS定义的可重写的方法:

  • protected boolean isHeldExclusively() : 是否在独占模式下被线程占用。只有用到condition才需要去实现它
  • protected boolean tryAcquire(int arg) : 独占方式。尝试获取资源,成功则返回true,失败则返回false
  • protected boolean tryRelease(int arg) :独占方式。尝试释放资源,成功则返回true,失败则返回false
  • protected int tryAcquireShared(int arg) :共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源
  • protected boolean tryReleaseShared(int arg) :共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false

如果我们需要实现一个自定义的同步器,就只需要继承AQS,然后根据需求去重写响应的方法,比如要实现独占锁,就实现tryAcquire(),tryRelease()方法,ReentrantLock就是这样做的,同样,要实现共享锁,就需要实现tryAcquireShared(),tryReleaseShared()方法,比如Semaphore,CountDownLatch,最后在要实现的组件中调用AQS中定义的模板方法

在ReentrantLock中,我们只需要实现对state进行简单的获取释放操作,至于获取资源失败,构建节点加入等待队列,线程阻塞唤醒一系列逻辑在AQS的模板方法中已经帮我们实现了。

AQS为我们定义好顶级逻辑的骨架,并提取出公用的线程入队列/出队列,阻塞/唤醒等一系列复杂逻辑的实现,将部分简单的可由使用者决定的操作逻辑延迟到子类中去实现即可

为什么上面定义的四个方法不是模板方法模式要求的抽象方法,让子类实现呢?

这是因为在独占锁中不需要实现tryAcquireShared(),tryReleaseShared()方法,而在共享锁中,也不需要tryAcquire(),tryRelease()方法,它们各自有自己的实现,如果定义成抽象方法,就必须实现所有,所以使用重写。

下面实现一个自定义的同步器:

public class SelfSynchronizer {
    private final Sync sync = new Sync();

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

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public boolean unLock() {
        return sync.release(1);
    }

    static class Sync extends AbstractQueuedSynchronizer {
        //是否处于占用状态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        /**
         * 获取同步资源
         * @param acquires
         * @return
         */
        @Override
        public boolean tryAcquire(int acquires) {
            if(compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            //这里没有考虑可重入锁
            /*else if (Thread.currentThread() == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }*/
            return false;
        }

        /**
         * 释放同步资源
         * @param releases
         * @return
         */
        @Override
        protected boolean tryRelease(int releases) {
            int c = getState() - releases;
            boolean free = false;
            if (c == 0) {
                free = true;
            }
            setState(c);
            return free;
        }
    }
}

ReentrantLock源码和上面自定义的同步器很相似,可以看下我的另一篇博客
测试下该同步器,i++在多线程下执行情况:

public class TestSelfSynchronizer {
    private static int a = 0;
    private static int b = 0;
    private static SelfSynchronizer selfSynchronizer = new SelfSynchronizer();
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 50, 1, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>());

    private static ExecutorService ec = Executors.newFixedThreadPool(20);
    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 20 ; i++) {
            executor.submit(new Task());
        }

        for (int j = 0; j < 20 ; j++) {
            ec.submit(new TaskSync());
        }
        Thread.sleep(10000);
        System.out.println("a的值:"+ a);
        System.out.println("b的值" + b);
        executor.shutdown();
        ec.shutdown();
    }

    static class Task implements Runnable {

        @Override
        public void run() {
            for(int i=0;i<10000;i++) {
                a++;
            }
        }
    }

    static class TaskSync implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
            	//使用同步器加锁
                selfSynchronizer.lock();
                b++;
                selfSynchronizer.unLock();
            }
        }
    }
}

开启两个线程池,对int型变量自增10000次,如果不加同步器,最后值小于200000,使用了自定义同步器则最后值正常等于200000,这是因为每次自增操作加锁

你可能感兴趣的:(设计模式,并发编程)