AbstractQueuedSynchronizer即抽象队列同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。 它维护了一个volatile int state(代表共享资源)和一个FIFO双端队列(多线程争用阻塞时线程进入此队列尾部,队列头节点是成功获取锁的线程,当头节点释放锁时,会唤醒后面节点并释放当前头节点的引用)。 AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。 不同自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。
独占锁的获取流程 (1)调用入口方法acquire(arg) (2)调用模版方法tryAcquire(arg)尝试获取锁,若成功则返回,若失败则走下一步 (3)将当前线程构造成一个Node节点,并利用CAS将其加入到同步队列尾部,然后该节点对应线程进入自旋状态 (4)自旋时首先判断其前驱节点释放为头节点&是否成功获取同步状态,两个条件都成立,则将当前线程的节点设置为头节点,如果不是,则利用LockSupport.park(this)将当前线程挂起 ,等待被前驱节点唤醒
独占锁的释放流程 共享锁的获取流程 (1)调用acquireShared(arg)入口方法 (2)进入tryAcquireShared(arg)方法获取同步状态,如果返回值>=0,说明同步状态(state)有剩余,获取锁成功直接返回 如果返回值<0,说明获取同步状态失败,向队列尾部添加一个共享类型的Node节点,随即该节点进入自旋状态 (3)自旋时,首先检查前驱节点释放为头节点&tryAcquireShared()是否>=0(即成功获取同步状态)。如果是则说明当前节点可执行,把当前节点设置为头节点并唤醒所有后继节点;如果否,则利用LockSupport.unpark(this)挂起当前线程,等待被前驱节点唤醒
共享锁的释放流程 重入锁 非公平锁与公平锁 非公平锁是指当锁状态为可用时,不管在当前锁上是否有其他线程在等待,新近线程都有机会抢占锁。 公平锁是指当多个线程尝试获取锁时,成功获取锁的顺序与请求获取锁的顺序相同。 AQS实现中两者区别在于是否判断当前节点存在前驱节点!hasQueuedPredecessors() &&,如果当前线程获取锁失败就会被加入到AQS同步队列,那么如果同步队列中的节点存在前驱节点,也就表明存在线程比当前节点线程更早获取锁,只有等待前面线程释放锁后才能获取锁。
读写锁 基于AQS的读写锁实现ReentrantReadWriteLock,该读写锁实现原理是:将同步变量state按照高16位和低16位拆分,高16位表示读锁,低16位表示写锁。 写锁的获取 (1)获取同步状态,从中分离出低16位的写锁状态 (2)如果同步状态不为0,说明存在读锁或写锁 (3)如果存在读锁(c !=0 && w == 0),则不能获取写锁(保证写对读的可见性) (4)如果当前线程不是上次获取写锁的线程,则不能获取写锁(写锁为独占锁) (5)如果以上判断均通过,则在低16位同步状态上利用CAS进行修改(增加写锁同步状态,实现可重入) (6)将当前线程设置为写锁的获取线程 写锁的释放与独占锁类似,不断减少读锁同步状态,同步状态为0时,写锁完全释放
读锁(共享锁)的获取 (1)获取当前同步状态 (2)计算高16为读锁状态 1后的值 (3)如果大于能够获取到的读锁的最大值,则抛出异常 (4)如果存在写锁并且当前线程不是写锁的获取者,则获取读锁失败 (5)如果上述判断都通过,则利用CAS重新设置读锁的同步状态 来源:http://www./content-4-120601.html
|