AQS

什么是AQS

java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。

JDK中提供的大多数的同步器如Lock, Latch, Barrier等,都是基于AQS框架来实现的

  • 一般是通过一个内部类Sync继承 AQS
  • 将同步器所有调用都映射到Sync对应的方法

AQS的实现
image.png

AQS的特性

  • 阻塞等待队列
  • 共享/独占
  • 公平/非公平
  • 可重入
  • 允许中断

AQS内部维护属性volatile int state

  • state表示资源的可用状态

    State三种访问方式:

  • getState()

  • setState()
  • compareAndSetState()

AQS定义两种资源共享方式

  • Exclusive-独占,只有一个线程能执行,如ReentrantLock
  • Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch

    AQS定义两种队列

  • 同步等待队列: 主要用于维护获取锁失败时入队的线程

  • 条件等待队列: 调用await()的时候会释放锁,然后线程会加入到条件队列,调用signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁

    AQS 定义了5个队列中节点状态:

  1. 值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
  2. CANCELLED,值为1,表示当前的线程被取消;
  3. SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
  4. CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
  5. PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;

不同自定义同步器共享资源的方式不同,
自定义同步器只需要实现共享资源的state的获取与释放方式即可,
底层的线程入队,出队,队列的维护AQS底层以实现。

自定义同步器实现主要实现以下几个方法

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

同步等待队列

一种基于双向链表数据结构的队列,是FIFO先进先出线程等待队列
线程由原自旋机制改为阻塞机制

AQS 依赖CLH同步队列来完成同步状态的管理:

  • 当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程
  • 当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
  • 通过signal或signalAll将条件队列中的节点转移到同步队列。(由条件队列转化为同步队列)

image.png

条件等待队列

AQS中条件队列是使用单向列表保存的,用nextWaiter来连接:

  • 调用await方法阻塞线程;
  • 当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列)

    Condition接口

    image.png
  1. 调用Condition#await方法会释放当前持有的锁,然后阻塞当前线程,同时向Condition队列尾部添加一个节点,所以调用Condition#await方法的时候必须持有锁。
  2. 调用Condition#signal方法会将Condition队列的首节点移动到阻塞队列尾部,然后唤醒因调用Condition#await方法而阻塞的线程(唤醒之后这个线程就可以去竞争锁了),所以调用Condition#signal方法的时候必须持有锁,持有锁的线程唤醒被因调用Condition#await方法而阻塞的线程。

    1. @Slf4j
    2. public class ConditionTest {
    3. public static void main(String[] args) {
    4. Lock lock = new ReentrantLock();
    5. Condition condition = lock.newCondition();
    6. new Thread(() -> {
    7. lock.lock();
    8. try {
    9. log.debug(Thread.currentThread().getName() + " 开始处理任务");
    10. condition.await();
    11. log.debug(Thread.currentThread().getName() + " 结束处理任务");
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. } finally {
    15. lock.unlock();
    16. }
    17. }).start();
    18. new Thread(() -> {
    19. lock.lock();
    20. try {
    21. log.debug(Thread.currentThread().getName() + " 开始处理任务");
    22. Thread.sleep(2000);
    23. condition.signal();
    24. log.debug(Thread.currentThread().getName() + " 结束处理任务");
    25. } catch (Exception e) {
    26. e.printStackTrace();
    27. } finally {
    28. lock.unlock();
    29. }
    30. }).start();
    31. }

    ReentrantLock

    基于AQS架构应用实现的,功能类似与Synchronized是一种互斥锁
    特点:

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与 synchronized 一样,都支持可重入

synchronized和ReentrantLock的区别

  • synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;
  • synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;、
  • synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的
  • synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;
  • 在发生异常时synchronized会自动释放锁,而ReentrantLock需要开发者在finally块中显示释放锁;
  • ReentrantLock获取锁的形式有多种:如立即返回是否成功的tryLock(),以及等待指定时长的获取,更加灵活
  • synchronized在特定的情况下对于已经在等待的线程是后来的线程先获得,而ReentrantLock对于已经在等待的线程是先来的线程先获得锁;

1648041782(1).png

reentrantLock 的使用

  1. ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
  2. ReentrantLock lock = new ReentrantLock(true); //公平锁
  3. //在 try 外加锁
  4. lock.lock();
  5. try {
  6. //临界区
  7. } finally {
  8. // 解锁
  9. lock.unlock();
  10. }

可重入

  1. @Slf4j
  2. public class ReentrantLockDemo2 {
  3. public static ReentrantLock lock = new ReentrantLock();
  4. public static void main(String[] args) {
  5. method1();
  6. }
  7. public static void method1() {
  8. lock.lock();
  9. try {
  10. log.debug("execute method1");
  11. method2();
  12. } finally {
  13. lock.unlock();
  14. }
  15. }
  16. public static void method2() {
  17. lock.lock();
  18. try {
  19. log.debug("execute method2");
  20. method3();
  21. } finally {
  22. lock.unlock();
  23. }
  24. }
  25. public static void method3() {
  26. lock.lock();
  27. try {
  28. log.debug("execute method3");
  29. } finally {
  30. lock.unlock();
  31. }
  32. }

可中断

  1. @Slf4j
  2. public class ReentrantLockDemo3 {
  3. public static void main(String[] args) {
  4. ReentrantLock lock = new ReentrantLock();
  5. Thread t1 = new Thread(() -> {
  6. log.debug("t1启动...");
  7. try {
  8. lock.lockInterruptibly();
  9. try {
  10. log.debug("t1获得了锁");
  11. } finally {
  12. lock.unlock();
  13. }
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. log.debug("t1等锁的过程中被中断");
  17. }
  18. }, "t1");
  19. lock.lock();
  20. try {
  21. log.debug("main线程获得了锁");
  22. t1.start();
  23. //先让线程t1执行
  24. try {
  25. Thread.sleep(1000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. t1.interrupt();
  30. log.debug("线程t1执行中断");
  31. } finally {
  32. lock.unlock();
  33. }
  34. }

锁超时

  1. @Slf4j
  2. public class ReentrantLockDemo4 {
  3. public static void main(String[] args) {
  4. ReentrantLock lock = new ReentrantLock();
  5. Thread t1 = new Thread(() -> {
  6. log.debug("t1启动...");
  7. // 注意: 即使是设置的公平锁,此方法也会立即返回获取锁成功或失败,公平策略不生效
  8. // if (!lock.tryLock(1, TimeUnit.SECONDS)) { 设置时间
  9. if (!lock.tryLock()) {
  10. log.debug("t1获取锁失败,立即返回false");
  11. return;
  12. }
  13. try {
  14. log.debug("t1获得了锁");
  15. } finally {
  16. lock.unlock();
  17. }
  18. }, "t1");
  19. lock.lock();
  20. try {
  21. log.debug("main线程获得了锁");
  22. t1.start();
  23. //先让线程t1执行
  24. try {
  25. Thread.sleep(1000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. } finally {
  30. lock.unlock();
  31. }
  32. }

公平锁
ReentrantLock 默认是不公平的

  1. @Slf4j
  2. public class ReentrantLockDemo5 {
  3. public static void main(String[] args) throws InterruptedException {
  4. ReentrantLock lock = new ReentrantLock(true); //公平锁
  5. for (int i = 0; i < 500; i++) {
  6. new Thread(() -> {
  7. lock.lock();
  8. try {
  9. try {
  10. Thread.sleep(10);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. log.debug(Thread.currentThread().getName() + " running...");
  15. } finally {
  16. lock.unlock();
  17. }
  18. }, "t" + i).start();
  19. }
  20. // 1s 之后去争抢锁
  21. Thread.sleep(1000);
  22. for (int i = 0; i < 500; i++) {
  23. new Thread(() -> {
  24. lock.lock();
  25. try {
  26. log.debug(Thread.currentThread().getName() + " running...");
  27. } finally {
  28. lock.unlock();
  29. }
  30. }, "强行插入" + i).start();
  31. }
  32. }

条件变量
java.util.concurrent类库中提供Condition类来实现线程之间的协调。调用Condition.await() 方法使线程等待,其他线程调用Condition.signal() 或 Condition.signalAll() 方法唤醒等待的线程。
注意:调用Condition的await()和signal()方法,都必须在lock保护之内。

  1. @Slf4j
  2. public class ReentrantLockDemo6 {
  3. private static ReentrantLock lock = new ReentrantLock();
  4. private static Condition cigCon = lock.newCondition();
  5. private static Condition takeCon = lock.newCondition();
  6. private static boolean hashcig = false;
  7. private static boolean hastakeout = false;
  8. //送烟
  9. public void cigratee(){
  10. lock.lock();
  11. try {
  12. while(!hashcig){
  13. try {
  14. log.debug("没有烟,歇一会");
  15. cigCon.await();
  16. }catch (Exception e){
  17. e.printStackTrace();
  18. }
  19. }
  20. log.debug("有烟了,干活");
  21. }finally {
  22. lock.unlock();
  23. }
  24. }
  25. //送外卖
  26. public void takeout(){
  27. lock.lock();
  28. try {
  29. while(!hastakeout){
  30. try {
  31. log.debug("没有饭,歇一会");
  32. takeCon.await();
  33. }catch (Exception e){
  34. e.printStackTrace();
  35. }
  36. }
  37. log.debug("有饭了,干活");
  38. }finally {
  39. lock.unlock();
  40. }
  41. }
  42. public static void main(String[] args) {
  43. ReentrantLockDemo6 test = new ReentrantLockDemo6();
  44. new Thread(() ->{
  45. test.cigratee();
  46. }).start();
  47. new Thread(() -> {
  48. test.takeout();
  49. }).start();
  50. new Thread(() ->{
  51. lock.lock();
  52. try {
  53. hashcig = true;
  54. //唤醒送烟的等待线程
  55. cigCon.signal();
  56. }finally {
  57. lock.unlock();
  58. }
  59. },"t1").start();
  60. new Thread(() ->{
  61. lock.lock();
  62. try {
  63. hastakeout = true;
  64. //唤醒送饭的等待线程
  65. takeCon.signal();
  66. }finally {
  67. lock.unlock();
  68. }
  69. },"t2").start();
  70. }

ReentrantLock源码分析

关注点:
关注点:
1. ReentrantLock加锁解锁的逻辑
2. 公平和非公平,可重入锁的实现
3. 线程竞争锁失败入队阻塞逻辑和获取锁的线程释放锁唤醒阻塞线程竞争锁的逻辑实现 ( 设计的精髓:并发场景下入队和出队操作)
tt.png

核心:AQS的入队,出队

  1. /**
  2. * Acquires in exclusive uninterruptible mode for thread already in
  3. * queue. Used by condition wait methods as well as acquire.
  4. *
  5. * @param node the node
  6. * @param arg the acquire argument
  7. * @return {@code true} if interrupted while waiting
  8. */
  9. final boolean acquireQueued(final Node node, int arg) {
  10. boolean failed = true;
  11. try {
  12. boolean interrupted = false;
  13. for (;;) {
  14. final Node p = node.predecessor();
  15. if (p == head && tryAcquire(arg)) {
  16. setHead(node);
  17. p.next = null; // help GC
  18. failed = false;
  19. return interrupted;
  20. }
  21. if (shouldParkAfterFailedAcquire(p, node) &&
  22. parkAndCheckInterrupt())
  23. interrupted = true;
  24. }
  25. } finally {
  26. if (failed)
  27. cancelAcquire(node);
  28. }
  29. }
  1. /**
  2. * Creates and enqueues node for current thread and given mode.
  3. *
  4. * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
  5. * @return the new node
  6. */
  7. private Node addWaiter(Node mode) {
  8. Node node = new Node(Thread.currentThread(), mode);
  9. // Try the fast path of enq; backup to full enq on failure
  10. Node pred = tail;
  11. if (pred != null) {
  12. node.prev = pred;
  13. if (compareAndSetTail(pred, node)) {
  14. pred.next = node;
  15. return node;
  16. }
  17. }
  18. enq(node);
  19. return node;
  20. }
  21. /**
  22. * Inserts node into queue, initializing if necessary. See picture above.
  23. * @param node the node to insert
  24. * @return node's predecessor
  25. */
  26. private Node enq(final Node node) {
  27. for (;;) {
  28. Node t = tail;
  29. if (t == null) { // Must initialize
  30. if (compareAndSetHead(new Node()))
  31. tail = head;
  32. } else {
  33. node.prev = t;
  34. if (compareAndSetTail(t, node)) {
  35. t.next = node;
  36. return t;
  37. }
  38. }
  39. }
  40. }
  41. static final class Node {
  42. /** Marker to indicate a node is waiting in shared mode */
  43. static final Node SHARED = new Node();
  44. /** Marker to indicate a node is waiting in exclusive mode */
  45. static final Node EXCLUSIVE = null;
  46. /** waitStatus value to indicate thread has cancelled */
  47. static final int CANCELLED = 1;
  48. /** waitStatus value to indicate successor's thread needs unparking */
  49. static final int SIGNAL = -1;
  50. /** waitStatus value to indicate thread is waiting on condition */
  51. static final int CONDITION = -2;
  52. /**
  53. * waitStatus value to indicate the next acquireShared should
  54. * unconditionally propagate
  55. */
  56. static final int PROPAGATE = -3;