- CountDownLatch:实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
- CyclicBarrier:通过它可以实现让一组线程等待至某个状态之后再全部同时执行
- Semaphore:信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
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)调用入口方法release(arg)
- (2)调用模版方法tryRelease(arg)释放同步状态
- (3)获取同步队列中当前节点的下一节点
- (4)利用LockSupport.unpark(currentNode.next.thread)唤醒后继节点
共享锁的获取流程
- (1)调用acquireShared(arg)入口方法
- (2)进入tryAcquireShared(arg)方法获取同步状态,如果返回值>=0,说明同步状态(state)有剩余,获取锁成功直接返回
如果返回值<0,说明获取同步状态失败,向队列尾部添加一个共享类型的Node节点,随即该节点进入自旋状态 - (3)自旋时,首先检查前驱节点释放为头节点&tryAcquireShared()是否>=0(即成功获取同步状态)。如果是则说明当前节点可执行,把当前节点设置为头节点并唤醒所有后继节点;如果否,则利用LockSupport.unpark(this)挂起当前线程,等待被前驱节点唤醒
共享锁的释放流程
- (1)调用releaseShared(arg)方法释放同步状态
- (2)如果释放成功,则遍历整个队列,利用LockSupport.unpark(nextNode.thread)唤醒所有后继节点
重入锁
- 重入锁指是当前线成功获取锁后,如果再次访问该临界区则不会对自己产生互斥行为。ReentrantLock和synchronized都是可重入锁,synchronized由jvm实现可重入,ReentrantLock基于AQS实现可重入。
- ReentrantLock可重入的原理是判断上次获取锁的线程是否为当前线程,如果是则可再次进入临界区,如果不是则阻塞。
非公平锁与公平锁
- 非公平锁是指当锁状态为可用时,不管在当前锁上是否有其他线程在等待,新近线程都有机会抢占锁。
- 公平锁是指当多个线程尝试获取锁时,成功获取锁的顺序与请求获取锁的顺序相同。
- 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重新设置读锁的同步状态
其他具体实现
-
CountDownLatch:允许一个或多个线程等待其他线程完成操作。如果想等待N个点完成则初始化时传入N,每调用一次countDown方法则N减1,await方法会阻塞当前线程,直到N等于0.
-
同步屏障CyclicBarrier:让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障才会开门,此时所有被拦截的线程才会继续运行。---- 初始化时传入要拦截的线程数,每个线程调用await表示到达屏障然后自己被阻塞。(适用于多线程计算数据,然后合并计算的场景)
-
控制并发线程数Semaphore:控制同时访问的线程数,协调合理分配资源。