细节爆炸!并发编程的半壁江山——AQS详解( 二 )


再以CountDownLatch为例 , 任务分为N个子线程去执行 , state也初始化为N(注意N要与线程个数一致) 。 这N个子线程是并行执行的 , 每个子线程执行完后countDown()一次 , state会CAS减1 。 等到所有子线程都执行完后(即state=0) , 会unpark()主调用线程 , 然后主调用线程就会从await()函数返回 , 继续后余动作 。
一般来说 , 自定义同步器要么是独占方法 , 要么是共享方式 , 他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可 。 但AQS也支持自定义同步器同时实现独占和共享两种方式 , 如ReentrantReadWriteLock 。
在acquire() acquireShared()两种方式下 , 线程在等待队列中都是忽略中断的 , acquireInterruptibly()/acquireSharedInterruptibly()是支持响应中断的 。
继承AQS,手写独占式可重入锁:说了那么多,但是说一千道一万不如自己手写试试,接下来看代码

package org.dance.day4.aqs;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.AbstractQueuedSynchronizer;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;/** * 采用主类实现Lock接口,内部类继承AQS,封装细节 * 自定义锁 * @author ZYGisComputer */public class CustomerLock implements Lock {private final Sync sync = new Sync();/*** 采用内部类来继承AQS,封装细节*实现独占锁,通过控制state状态开表示锁的状态*state:1 代表锁已被占用*state:0 代表锁可以被占用*/private static class Sync extends AbstractQueuedSynchronizer{@Overrideprotected boolean tryAcquire(int arg) {if(compareAndSetState(0,1)){// 当前线程获取到锁setExclusiveOwnerThread(Thread.currentThread());return true;}else{return false;}}@Overrideprotected boolean tryRelease(int arg) {// 如果状态为没人占用,还去释放,就报错if(getState()==0){throw new UnsupportedOperationException();}// 把锁的占用者制空setExclusiveOwnerThread(null);setState(0);return true;}/*** 判断线程是否占用资源* @return*/@Overrideprotected boolean isHeldExclusively() {return getState()==1;}/*** 获取Condition接口* @return*/public Condition getCondition(){return new ConditionObject();}}@Overridepublic void lock() {sync.acquire(1);}@Overridepublic void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}@Overridepublic boolean tryLock() {return sync.tryAcquire(1);}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return sync.tryAcquireNanos(1,unit.toNanos(time));}@Overridepublic void unlock() {sync.release(1);}@Overridepublic Condition newCondition() {return sync.getCondition();}}工具类:
package org.dance.tools;import java.util.concurrent.TimeUnit;/** * 类说明:线程休眠辅助工具类 */public class SleepTools {/*** 按秒休眠* @param seconds 秒数*/public static final void second(int seconds) {try {TimeUnit.SECONDS.sleep(seconds);} catch (InterruptedException e) {}}/*** 按毫秒数休眠* @param seconds 毫秒数*/public static final void ms(int seconds) {try {TimeUnit.MILLISECONDS.sleep(seconds);} catch (InterruptedException e) {}}}测试类:
package org.dance.day4.aqs;import org.dance.tools.SleepTools;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** *类说明:测试手写锁 */public class TestMyLock {public static void main(String[] args) {TestMyLock testMyLock = new TestMyLock();testMyLock.test();}public void test() {// 先使用ReentrantLock 然后替换为我们自己的Lockfinal Lock lock = new ReentrantLock();class Worker extends Thread {@Overridepublic void run() {while (true) {lock.lock();try {SleepTools.second(1);System.out.println(Thread.currentThread().getName());SleepTools.second(1);} finally {lock.unlock();}SleepTools.second(2);}}}// 启动10个子线程for (int i = 0; i < 10; i++) {Worker w = new Worker();w.setDaemon(true);w.start();}// 主线程每隔1秒换行for (int i = 0; i < 10; i++) {SleepTools.second(1);System.out.println();}}}执行结果:
Thread-0Thread-1Thread-2Thread-3Thread-4通过结果可以看出来每次都是只有一个线程在执行的,线程的锁获取没有问题,接下来换我们自己的锁
final Lock lock = new CustomerLock();再次执行测试
执行结果:
Thread-0Thread-1Thread-2Thread-3Thread-4由此可见,这个手写的锁,和ReentrantLock是一样的效果,是不是感觉也挺简单的,也没有多少行代码
那么独占锁,被一个线程占用着,其他线程去了哪里?不要走开接下来进入AQS的源码看看
理论:
细节爆炸!并发编程的半壁江山——AQS详解文章插图
在AQS中的数据结构是采用同步器+一个双向循环链表的数据结构,来存储等待的节点的,因为双向链表是没有头的,但是为了保证唤醒的操作,同步器中的head标志了链表中的一个节点为头节点,也就是将要唤醒的,也标识了一个尾节点