文章导读:
【原创】JUC包源码分析01 | ArrayBlockingQueue
【原创】JUC包源码分析02 | LinkedBlockingQueue
【原创】JUC包源码分析03 | PriorityBlockingQueue
【原创】JUC包源码分析04 | SynchronousQueue
【原创】JUC包源码分析05 | AbstractQueuedSynchronizer
【原创】JUC包源码分析06 | AbstractQueuedSynchronizer
备注:JDK版本:1.8
AbstractQueuedSynchronizer作为Java并发包下的并发类设计的基石,熟悉AbstractQueuedSynchronizer的使用,能够让我们快速的开发一款适合自己业务的并发工具。
AbstractQueuedSynchronizer提供了用于获取、释放锁的模板方法,开发者只需要复写该模板方法里部分逻辑,既可以自实现锁。
acquire():获取独占锁
release():释放独占锁
acquireShared():获取共享锁
releaseShared():释放共享锁
/*** 获取一个独占锁,不考虑被干扰.*/public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}/*** 释放独占锁,不考虑被干扰.*/public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}/*** 获取一个共享锁,不考虑干扰.*/public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0)doAcquireShared(arg);}/*** 释放一个共享锁.*/public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}
【原创】JUC包源码分析05 | AbstractQueuedSynchronizer小节中对上述4个方法的实现源码逻辑做了细致的介绍。其中的tryAcquire()、tryRelease()、tryAcquireShared()、tryReleaseShared()方法在AQS中均是未实现的方法,也就意味着这部分方法需要子类基于自身的业务场景实现。
这样的设计是合乎情理的,AQS只是作为一个并发锁实现的基石类,它并不需要知道一个并发锁的业务实现,这也给JDK底层多种不同锁的实现提供了可行性,为业务侧自行基于业务场景扩展锁提供了更多的可能。
本文将结合【原创】JUC包源码分析05 | AbstractQueuedSynchronizer的介绍,自定义一个独占(可重入)的锁,以加深对AQS底层设计的理解。
既然是独占锁,那么在锁的内部,必然会复写AQS的tryAcquire()、tryRelease()方法,具体的实现逻辑如下:
我们自定义一个类,继承AQS,复写它的tryAcquire()和tryRelease()方法:
/*** 复写AQS的tryAcquire()和tryRelease()方法*/public class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 0L;/*** 尝试获取锁方法** @param acquires 量* @return 成功:true,失败:false*/@Overrideprotected boolean tryAcquire(int acquires) {// 获取当前线程final Thread current = Thread.currentThread();int c = getState();// 如果没有线程获取到锁if (c == 0) {// CAS操作,为当前线程尝试获取锁if (compareAndSetState(0, acquires)) {// 进入到这里,代表一定获取了锁// 设置当前线程为独占锁线程setExclusiveOwnerThread(current);// 返回true,代表获取了锁return true;}} // 判断是否是当前线程重复获取锁(锁重入)else if (current == getExclusiveOwnerThread()) {// 进入到这里,代表是重入锁// 设置statesetState(c + acquires);// 返回true,代表获取了锁return true;}// CAS失败,又不是重入,那么锁一定是被其他线程占用着// 即代表没有获取到锁return false;}/*** 尝试释放锁** @param releases 量* @return 成功:true,失败:false*/@Overrideprotected boolean tryRelease(int releases) {// 直接将当前state减去相应的releasesint c = getState() - releases;// 必须是当前线程才能释放锁// 也就是获取锁的线程才能释放自己的锁if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 如果c == 0, 代表之前的锁已经释放if (c == 0) {// 此时没有线程持有锁free = true;// 没有线程持有锁setExclusiveOwnerThread(null);}// 设置回statesetState(c);// 返回true: 释放锁成功,没有线程持有锁// 返回false: 锁依然有线程在持有,可能是重入锁return free;}}
上述代码中,我们自行实现了锁的获取和释放逻辑,并对该逻辑进行了详细的分析。当然,这部分逻辑是基于我个人的业务场景,并不适合所有的业务,该示例代码只是为了跟大家说明如何复写AQS的方法,为下文中锁的实现提供支撑。
/*** Desp: 借用AQS实现可重入的独占锁* 2021-01-03 17:15* Created by zhru.*/public class SingleLock {/*** 复写AQS的tryAcquire()和tryRelease()方法*/private static final class Sync extends AbstractQueuedSynchronizer{private static final long serialVersionUID = 0L;/*** 尝试获取锁方法** @param acquires 量* @return 成功:true,失败:false*/@Overrideprotected boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}} else if (current == getExclusiveOwnerThread()) {setState(c + acquires);return true;}return false;}/*** 尝试释放锁** @param releases 量* @return 成功:true,失败:false*/@Overrideprotected boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}}// 定义变量private final Sync sync;/*** 默认构造方法*/public SingleLock() {this.sync = new Sync();}/*** 尝试获取锁*/public void tryLock() {// 直接调用我们自定义的tryAcquire()方法sync.tryAcquire(1);}/*** 获取锁*/public void lock() {// 调用我们自定义AQS子类的acquire()方法sync.acquire(1);}/*** 释放锁*/public void unlock() {// 调用我们自定义AQS子类的release()方法sync.release(1);}}
从上面的代码中,我们可以发现,借助AQS,可以快速的设计一种适合自身业务场景的锁对象。在本示例中,用了不足100行代码,实现了一个可以重入的锁的核心逻辑。希望感兴趣的小伙伴,也能够主动借助AQS实现一种自己的锁,以加深对AQS的使用和理解。
测试上述代码(以将一个值借用多线程不断的增加1为例):
// 不使用锁的使用对象static int oneNum = 0;// 使用自定义锁时的使用对象static int twoNum = 0;// 循环次数static int roundSize = 100000;public static void main(String[] args) throws Exception {// 为了方便,直接调用Executors工具类// 实际业务场景,不建议使用ExecutorService executorService = Executors.newFixedThreadPool(5);for (int i = 0; i < roundSize; i++) {executorService.execute(() -> addOne());}// ===========使用自定义的锁============SingleLock singleLock = new SingleLock();for (int i = 0; i < roundSize; i++) {executorService.execute(() -> {singleLock.lock();try {addTwo();} finally {singleLock.unlock();}});}Thread.sleep(5000L);System.out.println("使用自定义锁执行结果:" + twoNum);executorService.shutdown();}// 自增1private static void addOne() {oneNum++;}// 自增1private static void addTwo() {twoNum++;}
执行结果如下:

上述结果表明未使用锁时,0自增100000次的结果明显小于100000。而使用我们自定义的锁时,上述计算过程从并行变成了串行,结果为100000,也间接证明了这点。
因作者能力有限,文中可能出现描述不清的地方,不足之处请指出!希望与你一起共进步^




