MST

星途 面试题库

面试题:Java的AQS中,acquire方法是如何实现独占式获取同步状态的?请结合源码分析。

在Java AQS源码中,acquire方法用于独占式获取同步状态。请详细分析该方法的执行流程,包括尝试获取同步状态(tryAcquire方法)、线程入队、自旋等待以及被唤醒后重新尝试获取同步状态等关键步骤,并说明每一步在整个同步机制中的作用和意义。
38.5万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. acquire方法整体概述

acquire方法是AQS(AbstractQueuedSynchronizer)中用于独占式获取同步状态的核心方法。其主要目的是获取同步状态,如果获取失败则将当前线程加入等待队列并阻塞,直到获取到同步状态。

2. 尝试获取同步状态(tryAcquire方法)

  • 执行流程:在acquire方法中,首先调用tryAcquire方法尝试获取同步状态。这个方法是由具体的同步器(如ReentrantLock)实现的。例如在ReentrantLock中,tryAcquire会检查当前状态是否为0(表示锁未被占用),如果是则尝试设置当前线程为持有锁的线程,并将同步状态设为1;如果同步状态不为0且当前线程是持有锁的线程,则将同步状态加1(实现可重入)。
  • 作用和意义tryAcquire方法体现了AQS的模板方法模式,将具体获取同步状态的逻辑交给子类实现。它允许在不进入等待队列的情况下快速获取同步状态,提高了同步的效率。如果tryAcquire成功,意味着当前线程成功获取到锁,不需要进入等待队列等待,减少了线程上下文切换的开销。

3. 线程入队

  • 执行流程:当tryAcquire方法获取同步状态失败时,当前线程会通过addWaiter方法创建一个新的节点(Node),并将其加入到同步队列的尾部。在加入队列过程中,会使用compareAndSetTail方法进行CAS操作来保证线程安全地将新节点加入队列。
  • 作用和意义:线程入队操作将获取同步状态失败的线程组织成一个FIFO队列,保证了公平性(如果是公平锁)。同时,通过队列的方式可以管理多个等待获取同步状态的线程,避免线程因为竞争锁而无序地反复尝试获取,降低系统的整体开销。

4. 自旋等待

  • 执行流程:线程入队后,会进入一个for(;;)无限循环(自旋)中,在循环中通过shouldParkAfterFailedAcquire方法判断是否应该挂起线程。如果前继节点是Node.SIGNAL状态,表示前继节点释放同步状态时会通知当前节点,此时当前线程可以安全地挂起等待唤醒;否则,通过compareAndSetWaitStatus方法尝试将前继节点状态设为Node.SIGNAL。如果挂起条件满足,则调用LockSupport.park(this)方法将当前线程挂起。
  • 作用和意义:自旋等待机制避免了不必要的线程挂起和唤醒操作。线程在一定程度上可以通过自旋来避免被挂起,因为线程的挂起和唤醒会带来较大的开销。通过自旋,线程可以在较短时间内等待同步状态的释放,减少线程上下文切换的开销。只有在自旋一段时间后仍无法获取同步状态时,才会挂起线程,提高了同步的整体效率。

5. 被唤醒后重新尝试获取同步状态

  • 执行流程:当持有同步状态的线程释放同步状态时,会唤醒等待队列中的头节点后继节点对应的线程。被唤醒的线程会从LockSupport.park(this)处恢复执行,重新进入acquireQueued方法中的自旋循环,再次调用tryAcquire方法尝试获取同步状态。如果获取成功,则将自己设置为头节点(通过setHead方法),并将原头节点出队(原头节点的后继节点变为新头节点),表示当前线程已经成功获取到同步状态。
  • 作用和意义:被唤醒后重新尝试获取同步状态是整个同步机制的关键环节,它保证了等待队列中的线程在同步状态释放后有机会重新竞争获取同步状态。通过将获取到同步状态的线程设置为头节点,进一步优化了队列的管理,使得后续获取同步状态的线程能够更快地获取到锁,提高了同步机制的整体性能。