前置知识-链表的优势:
- 灵活性:链表是动态的,而非数组一样的大小固定,对于插入和删除效率比较友好;
- 内存利用率高:由于链表不像数组一样需要开辟一块连续的空间,利用的是碎片空间,因此对比数组,链表对空间的使用率更高;
- 基础的数据结构:队列、栈、redis等,是实现各种数据结构的基础,尤其更是学习redis底层数据结构必备的知识储备;
正文:
AQS的底层就是采用双向链表,主要包括管理线程队列、快速的插入和删除、动态管理线程的状态、避免线程的阻塞和唤醒的开销。
优势:
- FIFO(先进先出):可在队列的前端和后端进行插入和删除、确保这按照锁的请求顺序进行处理。
- 高效:双向链表保证了可以双向遍历,而不是只从一个方向查找,保证了高并发情况下的高效运行。
- 管理线程状态:AQS使用了一个内部类Node来表示等待队列中的节点,这些节点通过双向链表连接。这些节点可以很容易地跟踪线程的状态,例如,是否已被取消、是否被中断等。
- 避免线程阻塞和唤醒的开销:刚加入到链表的线程首先会通过自旋的方式尝试去竞争锁,只有头节点的下一个节点才有必要去竞争锁,后续的节点竞争锁的意义不大,这样可以避免不必要的线程阻塞和唤醒的开销。
问题:为什么不采用单向链表?
解答:首先,单向链表的尾部节点只会记录下一个节点的位置,而不会记录上一个节点的,因此单向链表只会从一个方向查找,双向链表的节点会同时前驱和后继节点,因此无论从哪一个方向查找都可以,对于某些需要双向检索的同步操作和线程调度非常有用。
其次:后继节点,需要判断前驱节点的锁的状态,来判断本身节点是否需要继续阻塞等待,单向链表满足不了,因为找不到上一个节点,当然也无法判断。再有双向链表在线程等待的过程中支持高效的删除,以此移除取消和中断的线程节点。
最后:双向链表支持线程在队列中移动,AQS中的等待队列是一个动态的数据结构,需要支持在任意位置插入和删除节点。双向链表可以很方便地支持在任意位置插入和删除节点,而单向链表则需要从头节点开始遍历找到要插入或删除的节点位置,时间复杂度较高。