美文网首页
深入分析大厂面试题三

深入分析大厂面试题三

作者: Java及SpringBoot | 来源:发表于2021-01-25 17:26 被阅读0次

1 AbstractQueuedSynchronizer之AQS

1.1 先从字节跳动及其它大厂面试题说起

  • 从集合开始吧,介绍一下常用的集合类,哪些是有序的,哪些是无序的
  • hashmap是如何寻址的,哈希碰撞后是如何存储数据的,1.8后什么时候变成红黑树、说下红黑树原理有什么好处?
  • reentrantlock实现原理,简单说下AQS
  • synchronized实现原理,monitor对象是什么时候生成的?知道monitor和monitorenter和monitorexit这2个是怎么保证同步的吗,或者说底层是如何执行的
  • 偏向锁和轻量级锁有什么区别?

1.2 前置知识

  • 公平锁和非公平锁
  • 可重入锁
  • LockSupport
  • 自旋锁
  • 数据结构之链表
  • 设计模式之模板设计模式

1.3 抽象的队列同步器

image-20210125150805484.png
AbstractOwnableSynchronizer
AbstractQueuedLongSynchronizer
AbstractQueuedSynchronizer                                      
通常地: AbstractQueuedSynchronizer简称为AQS

是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量 表示持有锁的状态

image-20210121174820152.png

1.4 AQS为什么是JUC内容中最重要的基石

和AQS有关的

image-20210121174937297.png

ReentrantLock

image-20210125151522221.png

CountDownLatch

image-20210125151625649.png

ReentrantReadWriteLock

image-20210125152612032.png

Semaphore

image-20210125152651961.png

进一步理解锁和同步器的关系

  • 锁,面向锁的使用者
    • 定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
  • 同步器,面向锁的实现者
    • 比如Java并发大神Douglee,提出统一规 范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。

能干嘛

  • 加锁会导致阻塞
    • 有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
抢到资源的线程直接使用办理业务,抢占不到资源的线程的必然涉及一种排队等候机制,抢占资源失败的线程继续去等待(类似办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
 
既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
 
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node) ,通过CAS、自旋以及LockSuport.park()的方式,维护state变量的状态,使并发达到同步的效果。

1.5 AQS初步

1.5.1 AQS初识

image-20210122124943048.png

有阻塞就需要排队,实现排队必然需要队列

AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的 FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成 一个Node节点来实现锁的分配,通过CAS完成对State值的修改。

image-20210125162753991.png

1.5.2 AQS内部体系架构

image-20210122125108960.png

AQS自身

  • AQS的int变量

    • AQS的同步状态State成员变量
    /**
     * The synchronization state.
     */
    private volatile int;
    
    //银行办理业务的受理窗口状态
    //零就是没人,自由状态可以办理
    //大于等于1,有人占用窗口,等着
    
  • AQS的CLH队列

    • CLH队列(三个大牛的名字组成),为一个双向队列
  • state变量+CLH双端Node队列

image-20210122132651197.png

内部类Node(Node类在AQS类内部)

  • Node的int变量
    • Node的等待状态waitState成员变量 volatile int waitStatus
    • 等候区其它顾客(其它线程)的等待状态
    • 队列中每个排队的个体就是一个Node.
image-20210125163846064.png image-20210122132912772.png

1.5.3 AQS同步队列的基本结构

是用LockSupport.pork()来进行排队的

image-20210122133036764.png

1.6 AQS详解(ReentrantLock开始解读AQS)

1.6.1 Code

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class AQSDemo {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        //带入一个银行办理业务的案例来模拟我们的AQS如何进行线程的管理和通知唤醒机制
        //3个线程模拟3个来银行网点,受理窗口办理业务的顾客
        //A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----A thread come in");
                try {
                    TimeUnit.MINUTES.sleep(20);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }
        }, "A").start();

        //第二个顾客,第二个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待,
        //进入候客区
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----B thread come in");
            } finally {
                lock.unlock();
            }
        }, "B").start();

        //第三个顾客,第三个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待,
        //进入候客区
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("-----C thread come in");
            } finally {
                lock.unlock();
            }
        }, "C").start();
    }
}

Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的

1.6.2 ReentrantLock原理

image-20210122133555270.png

1.6.3 lock方法的公平和非公平

通过ReentrantLock的源码来讲解公平锁和非公平锁

image-20210125164307411.png

默认创建的是非公平锁

image-20210122133705885.png
可以明显看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:
hasQueuedPredecessors()
hasQueuedPredecessors是公平锁加锁时判断等待队列中是否存在有效节点的方法

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
对比公平锁和非公平锁的tryAcqure()方法的实现代码, 其实差别就在于非公平锁获取锁时比公平锁中少了一个判断!hasQueuedPredecessors()
    
hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:
 
公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;
 
非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(), 之后还是需要竞争锁(存在线程竞争的情况下)
image-20210122134046648.png
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

1.6.4 AQS源码深度分析

lock()

image-20210125164849255.png

acquire()

image-20210122134242173.png image-20210122134252774.png

tryAcquire(arg) 非公平锁

image-20210122134347797.png image-20210122134418124.png

继续推进条件,走下一步方法addWaiter,否则结束。

addWaiter(Node.EXCLUSIVE)

image-20210122134519595.png image-20210122134534059.png

双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位。 真正的第一个有数据的节点,是从第二个节点开始的。

假如3号ThreadC线程进来,prev-compareAndSetTail-next

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

image-20210122134705598.png

假如再抢抢失败就会进入shouldParkAterFailedAcquire和parkAndCheckInterrupt方法中

image-20210122134736962.png

shouldParkAfterFailedAcquire

image-20210122134756725.png image-20210122134802529.png

如果前驱节点的waitstatus是SIGNAL状态(-1),即shouldParkAfterFailedAcquire方法会返回true 程序会继续向下执行parkAndCheckInterrupt方法,用于将当前线程挂起

parkAndCheckInterrupt

image-20210122134829449.png

方法unlock()

sync.release(1);
tryRelease(arg)
unparkSuccessor
image-20210122140202420.png

1.6.5 AQS面试考点

你应该看过源码了,那么AQS里面有个变量叫State,它的值有几种?
3个状态:没占用是0,占用了是1,大于1是可重入锁

如果AB两个线程进来了以后,请问这个总共有多少个Node节点?
答案是3个
image-20210122142648284.png

相关文章

网友评论

      本文标题:深入分析大厂面试题三

      本文链接:https://www.haomeiwen.com/subject/suxizktx.html