定义:封装一个模板结构,将具体内容延迟到子类去实现
将多个类共有的方法和功能抽取出来,封装到抽象类,对于这些公有方法(模板方法)使用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,这是因为每次自增操作加锁