【517、说一下 AQS?看过锁的源码吗?说下 reent lock 的实现。】

32 阅读3分钟

AQS(AbstractQueuedSynchronizer)是 Java 并发包中一个重要的同步工具类,它提供了一个基于 FIFO 队列的同步器框架,可以方便地实现各种同步器,如 ReentrantLock、Semaphore、CountDownLatch 等。

AQS 中的主要概念是 state(状态)和 Node(节点)。其中 state 表示同步器的状态,具体含义由各个同步器自行定义;Node 表示线程在同步器上的等待状态,具体包含线程本身以及前驱和后继节点等信息。

ReentrantLock 是基于 AQS 实现的重入锁,其主要思想是在同步器中维护一个 state 变量表示锁的占用状态,同时使用一个双向链表维护等待线程队列。当一个线程请求获取锁时,如果锁已被占用,则将当前线程加入等待队列中,并将其挂起,直到锁被释放后唤醒该线程。同时,该线程的前驱节点将被设置为当前锁的占用线程,表示其需要等待前驱节点释放锁后才能继续执行。

ReentrantLock 的源码实现可以分为公平锁和非公平锁两种情况,其中公平锁会按照线程请求获取锁的顺序进行获取,而非公平锁则允许当前正在占用锁的线程再次获取锁,从而提高了并发性能。

公平锁的实现基于 AQS 中的 FIFO 队列,当一个线程请求获取锁时,会先将其加入等待队列中。如果当前锁的状态为未被占用或者当前线程是队列中的第一个节点,则获取锁成功;否则,当前线程将被挂起,等待前驱节点释放锁后再次尝试获取锁。在释放锁的过程中,当前锁的状态会被置为未被占用,并尝试唤醒后继节点,从而使得下一个等待线程可以继续执行。

非公平锁的实现则是在尝试获取锁时,直接尝试将 state 变量增加 1,并判断是否获取到了锁。如果获取到了锁,则直接返回;否则,当前线程将被加入等待队列中,并尝试在等待队列中获取前驱节点。如果前驱节点是当前锁的占用线程,则当前线程尝试获取锁成功,否则当前线程将被挂起,等待前驱节点释放锁后再次尝试获取锁。在释放锁的过程中,如果当前队列中还有等待线程,则会唤醒队列中的下一个节点,从而使得下一个等待线线程可以继续执行。

ReentrantLock 还提供了可重入的特性,即线程可以多次获取同一把锁,而不会发生死锁。当线程重入时,会将重入的次数保存在 AQS 中,并在最终释放锁的时候再进行减少。这样可以确保同一个线程在占用锁的过程中不会被其他线程阻塞,从而提高并发性能。

ReentrantLock 与 synchronized 的区别在于,前者提供了更高的并发性能和更灵活的特性,同时也更加复杂,需要手动进行加锁和解锁操作。后者则是 Java 内置的同步机制,可以自动进行加锁和解锁,但并发性能相对较低。在使用时,可以根据具体的场景和需求来选择使用哪种锁。