AQS解析

  • 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:控制同时访问的线程数,协调合理分配资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值