面试02——线程、HashMap、、、

一、线程相关

1、线程池原理,讲讲线程池里面的核心参数,你平时是怎么用线程池的

基本思想:预先创建多个线程对象,放入线程池,执行完后调用,用完还原到线程池。

优点:

* 提高线程的利用率
* 提高程序的响应速度
* 便于统一管理线程对象
* 可以控制最大并发数
import org.junit.Test;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author cpx
 * @version V1.0
 * @date 2021/09/28
 **/
public class test {

    @Test
    public void threadLocal(){
        /**
         * corePoolSize:核心线程数(3个核心窗口)
         * maximumPoolSize:最大线程窗口(5个)
         * keepAliveTime:存活时间(1L)
         * TimeUnit:时间单位(秒)
         * ArrayBlockingQueue:等待队列(3个)
         * Executors.defaultThreadFactory():线程工厂
         * ThreadPoolExecutor.AbortPolicy():拒绝策略(直接抛异常)
         * 若i = 9 ,则拒绝超出最大线程数量
         */
        ExecutorService executor = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0 ; i<8 ; i++){
            executor.execute(()->System.out.println(Thread.currentThread().getName() + "===>办理业务"));
        }
        executor.shutdown();
    }
}


2、线程池的应用与配置(见1);

3、谈谈你理解的IO和多线程

IO:是一种数据的流从源头留到目的地。比如文件拷贝,输入流和输出流都包括了。输入流从文件中读取数据存储到进程中,输出流从进程中读取数据然后写入 到目标文件。


多线程:所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间。由于线程运行在单一进程中,因此共享这个进程虚拟地址空间的整个内容,包括代码、数据、堆、共享库和打开的文件。

多线程的优点:

  • 无序跨进城边界;
  • 程序逻辑和控制方式简单;
  • 所有线程可以直接共享内存和变量等;
  • 线程方式小号的总资源比进程方式好

缺点:

  • 每个线程与主线程序共用地址空间,受限于2GB地址空间;
  • 线程之间的同步和加锁控制比较麻烦;
  • 一个线程的崩溃可能影响整个程序的稳定性
  • 达到一定的线程数量程度后,即使在增加CPU也无法提高性能

进程:是一个正在执行中的程序。每一个进程执行都有一个执行路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行,一个进程至少有一个线程。

4、网络io什么的

4.1、网络I/O的4个要素

网络IO首先是一种I/O,按照我们上一节中对于“I/O”的定义,网络I/O也符合4个要素:

  • 源头(source):一台电脑的磁盘或内存;
  • 目标(target):另一台电脑的磁盘或内存;
  • 通道(channel):网线,WiFi等;
  • 液体(fluid):数据(通常是字节形式)。

4.2 、5种不同的I/O模型

然而网络I/O相对与其他I/O更为复杂,因此Unix提供了5种不同的I/O模型,分别是

  • 阻塞I/O(blocking I/O)
  • 非阻塞I/O(non-blocking I/O)
  • I/O复用(I/O multiplexing)
  • 信号驱动式I/O(signal-driven I/O)
  • 异步I/O(asynchronous I/O)

面试02——线程、HashMap、、、_第1张图片

BIO

让我们先从BIO,即阻塞IO(blocking I/O)说起:

通常网络I/O在建立链接后,数据的传输通常包括俩个阶段

  • 阶段一:等待数据传输到本服务器;
  • 阶段二:内核将接受到的数据由"内核缓冲区"复制到“应用缓冲区”;

对应阻塞IO而言,这两个阶段又被分为了4个步骤:

  1. 用户的应用线程调用recvfrom,开始读取数据,进入阻塞状态;
  2. 系统内核等待接收网络传输的数据;(阶段一)
  3. 系统内核将传输的数据由内核缓冲区拷贝到应用缓冲区;(阶段二)
  4. 应用进程从阻塞中恢复,读取应用缓冲区的数据;
备注:
  1. “阻塞”可以理解为线程被内核“挂起”,让出CPU使用权,并等待被“唤醒”,重新获取CPU使用权;
  2. recvfrom()为Unix函数,类似于在JAVA编程中,我们使用read()来读取网络传输的数据;

面试02——线程、HashMap、、、_第2张图片

BIO的缺点:

1.在数据传输与复制过程中,线程都处于阻塞状态,因此在存在大量访问链接时,应用必须启用多个线程进行处理,系统需要为每个线程都分配相应的内存资源;

2.线程的切换成本是很高的。操作系统发生线程切换的时候,需要保留线程的上下文,然后执行系统调用。如果线程数过高,可能执行线程切换的时间甚至会大于线程执行的时间;

总结:BIO由于在数据传输与数据拷贝两个阶段,都需要阻塞线程,因此很难满足高并发,大量客户端同时访问服务端,传输数据的要求。因此在实际开发中,很少使用;或仅限于少量客户端链接的场景;

面试题:

BIO真的就一无是处了吗,哪些场景BIO的效率会更优秀呢?

答:我们说BIO是无法满足“大量链接”同时访问,传输数据的请求;但如果是链接数量较少,但单一链接的传输数据量较大的情况下,因为BIO省去了频繁询问的过程(这个后面NIO会讲),也是依然适用于这种模型的。

5、实现多线程有哪些方式

  • 第一种 实现Runnable接口,覆写run()方法
  • 第二种 继承Thread类,覆写run()方法
  • 第三种 利用Callable接口、Executors工具类、ExecutorService接口、Future接口实现有返回结果的多线程

5.1、第一种 实现Runnable接口,覆写run()方法

① 自定义类并实现Runnable接口,覆写run()方法

② 创建Thread对象,用实现了Runnable接口的类的实例对象作为参数实例化该Thread对象

③ 调用Thread的start()方法来启动线程

 1 package com.test;
 2 
 3 class MyThread implements Runnable{  // ①
 4     @Override
 5     public void run() {
 6         System.out.println("Thread body");
 7     }
 8 }
 9 public class MultiThread {
10     public static void main(String[] args){
11         Thread thread1 = new Thread(new MyThread());  // ②
12         Thread thread2 = new Thread(new MyThread());
13         thread1.start();  // ③
14         thread2.start();
15     }
16 }

5.2、第二种 继承Thread类,覆写run()方法

① 自定义类并继承Thread类,覆写run()方法

② 创建自定义类的实例对象

③ 调用自定义类对象的start()方法来启动线程

 1 package com.test;
 2 
 3 class MyThread extends Thread{  // ①
 4     @Override
 5     public void run() {
 6         System.out.println("Thread body");
 7     }
 8 }
 9 public class MultiThread {
10     public static void main(String[] args){
11         Thread thread1 = new MyThread();  // ②
12         Thread thread2 = new MyThread();
13         thread1.start();  // ③
14         thread2.start();
15     }
16 }

5.3 第三种 利用Callable接口、Executors工具类、ExecutorService接口、Future接口实现有返回结果的多线程

① 自定义类实现Callable接口,并覆写call()方法

② 利用Executors创建一个线程池 ExecutorService接口的实例对象

③ 调用线程池的submit()方法来启动线程,该方法会返回一个Future接口的实例对象

④ 调用Future实例对象的get()方法,获得线程的返回结果

 1 package com.test;
 2  
 3 import java.util.concurrent.*;
 4  
 5 class MyThread implements Callable<String>{  // ① 创建线程
 6      @Override
 7      public String call() {
 8          return "Thread body";
 9      }
10 }
11 public class MultiThread3 {
12     public static void main(String[] args){
13         ExecutorService threadPool = Executors.newSingleThreadExecutor();  // ② 创建线程池
14         Future<String> future = threadPool.submit(new MyThread());  // ③ 想线程池提交任务
15         try {
16             System.out.println(future.get());  // ④ 获得return的值
17         } catch (InterruptedException | ExecutionException e) {
18             e.printStackTrace();
19         }
20         threadPool.shutdown()  // 关闭线程池
21     }
22 }

Callable接口提供了比Runnable接口更强大的功能

1)Callable接口的call()方法可以在任务结束后提供一个返回值,Runnable接口的run()方法无法提供这个功能

2)Callable接口的call()方法可以抛出异常,Runnable接口的run()方法无法提供这个功能

3)ExecutorService接口的submit()方法可以得到一个Future接口的实例对象,可使用Future对象来监视目标线程调用call()方法的情况,当调用Future对象的get()方法以获取结果时,当前线程会阻塞,知道call()方法结果返回结果。

以上三种方式中,前两种方式线程执行完后没有返回值,只有第三种方式是带返回值的

当需要实现多线程时,一般推荐实现Runnable接口的方式

6.线程的创建方法

核心线程池的核心参数(见下方1)
阻塞队列
阻塞策略

三类阻塞队列:
//1 有界队列
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出(FIFO)队列,支持公平锁和非公平锁,有界
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出(FIFO)队列,默认长度为 Integer.MaxValue 有OOM危险,有界
workQueue = new LinkedBlockingDeque(); //一个由链表结构组成的,双向阻塞队列,有界
//2 无界队列
workQueue = new PriorityBlockingQueue(); //支持优先级排序的无限队列,默认自然排序,可以实现 compareTo()方法指定排序规则,不能保证同优先级元素的顺序,无界。
workQueue = new DelayQueue(); //一个使用优先级队列(PriorityQueue)实现的无界延时队列,在创建时可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。
workQueue = new LinkedTransferQueue(); //一个由链表结构组成的,无界阻塞队列
//3 同步移交队列
workQueue = new SynchronousQueue<>();//无缓冲的等待队列,队列不存元素,每个put操作必须等待take操作,否则无法添加元素,支持公平非公平锁,无界

四种拒绝策略:
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务

五种线程池:
ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多

public static void main (String[] args) throws Exception {
    testThreadPoolExecutor();
}
public static void testThreadPoolExecutor() throws Exception {
    //基础参数
    int corePoolSize=2;//最小活跃线程数
    int maximumPoolSize=5;//最大活跃线程数
    int keepAliveTime=5;//指定线程池中线程空闲超过 5s 后将被回收
    TimeUnit unit = TimeUnit.SECONDS;//keepAliveTime 单位
    //阻塞队列
    BlockingQueue<Runnable> workQueue = null;
	//1 有界队列
	workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出(FIFO)队列,支持公平锁和非公平锁,有界
	workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出(FIFO)队列,默认长度为 Integer.MaxValue 有OOM危险,有界
	workQueue = new LinkedBlockingDeque(); //一个由链表结构组成的,双向阻塞队列,有界
	//2 无界队列
	workQueue = new PriorityBlockingQueue(); //支持优先级排序的无限队列,默认自然排序,可以实现 compareTo()方法指定排序规则,不能保证同优先级元素的顺序,无界。
	workQueue = new DelayQueue(); //一个使用优先级队列(PriorityQueue)实现的无界延时队列,在创建时可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。
	workQueue = new LinkedTransferQueue(); //一个由链表结构组成的,无界阻塞队列
	//3 同步移交队列
	workQueue = new SynchronousQueue<>();//无缓冲的等待队列,队列不存元素,每个put操作必须等待take操作,否则无法添加元素,支持公平非公平锁,无界
 
 
    //拒绝策略
    RejectedExecutionHandler rejected = null;
    rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
    rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
    rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
    rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
 
 
    //使用的线程池
    ExecutorService threadPool = null;
    threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
    threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
    threadPool = Executors.newScheduledThreadPool(2);
    threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
    threadPool = new ThreadPoolExecutor(
            corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            unit,
            workQueue,
            rejected);//默认线程池,可控制参数比较多
    //执行无返回值线程
    TaskRunnable taskRunnable = new TaskRunnable();
    threadPool.execute(taskRunnable);
    List<Future<String>> futres = new ArrayList<>();
    for(int i=0;i<10;i++) {
        //执行有返回值线程
        TaskCallable taskCallable = new TaskCallable(i);
        Future<String> future = threadPool.submit(taskCallable);
        futres.add(future);
    }
    for(int i=0;i<futres.size();i++){
        String result = futres.get(i).get();
        System.out.println(i+" result = "+result);
    }
}
/**
    * 返回值的线程,使用 threadpool.execut() 执行
    */
public static class TaskRunnable implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " runnable result!");
    }
}
/**
    * 有返回值的线程,使用 threadpool.submit() 执行
    */
public static class TaskCallable implements Callable<String>{
    public TaskCallable(int index){
        this.i=index;
    }
    private int i;
    @Override
    public String call() throws Exception {
        int r = new Random().nextInt(5);
        try {
            Thread.sleep(r);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //System.out.println("callable result!");
        return Thread.currentThread().getName()+" callable index="+i +",sleep="+r;
    }
}


7、线程的五种状态,有哪些方法控制他们状态,wait和sleep的区别,

7.1、线程通常有五种状态:

  1. 新建状态(New):新创建了一个线程对象。
  2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待 获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
  5. 5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

7.2、阻塞的情况分为三种:

  1. 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,wait是object类的方法
  2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
  3. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法。

7.3、Java中sleep,wait,yield,join的区别

1.sleep()方法

  • 在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”.不推荐使用.
  • sleep()使当前线程进入阻塞状态,在指定时间内不会执行。

2.wait()方法

  • 在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
  • 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
  • 唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
  • waite()和notify()必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

3.yield方法

  • 暂停当前正在执行的线程对象。
  • yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
  • yield()只能使同优先级或更高优先级的线程有执行的机会。
  • 调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

4.join方法

  • 等待该线程终止。
  • 等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
  • 在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

注意:sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会
join的意思是会等到调用改join方法的线程执行完毕之后才会执行其他线程,举例说明:

public static void main(String[] args) throws InterruptedException {
//开启一个线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是新开副线程的执行");
//这个方法会先让副线程沉睡1秒之后在执行,但是期间不会影响主线程的执行
Thread.sleep(1000L);
}
});
thread.start();//启动线程
//这个方法会等副线程执行完成之后才会接下来执行主线程,会影响主线程的执行
thread.join();

System.out.println("我是主线程的执行");
}

8、threadlocal线程复用问题

首当前线程ThreadLocal数据未释放的时候,又被其它请求复用了,从而导致ThreadLocal数据混乱。
下图为tomcat配合线程池内容:

8.1、解决方案

1)在每次使用ThreadLocal来进行存储数据的时候,先进行赋值操作,即是校验或者其他操作没有成功,最起码需要一个默认的赋值。记得在使用的时候进行判断。
2)可以在调用的finally里面使用remove。

9.线程池

二、必问相关

1、特别是jvm,hashmap,redis,mq这几个

2、hashmap,秒杀,jvm内存、jc,spring 生命周期bean、ioc、aop,sql优化,springboot自动装配原理,分布式锁,es,mq,分布式事物,这些都是高频面试问题,前面几个是必问

1.HashMap

我们知道HashMap是线程不安全的,在多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

2.Spring生命周期bean

Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。

面试02——线程、HashMap、、、_第3张图片

面试02——线程、HashMap、、、_第4张图片

若容器注册了以上各种接口,程序那么将会按照以上的流程进行。下面将仔细讲解各接口作用。

二、各种接口方法分类

Bean的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:

1、Bean自身的方法  :  这个包括了Bean本身调用的方法和通过配置文件中的init-method和destroy-method指定的方法

2、Bean级生命周期接口方法  :  这个包括了BeanNameAware、BeanFactoryAware、InitializingBean和DiposableBean这些接口的方法

3、容器级生命周期接口方法  :  这个包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。

4、工厂后处理器接口方法  :  这个包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器  接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

3.IOC也叫依赖注入(DI)

IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

4.AOP(面向切面)

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

5.springboot自动装配原理**

看看SpringBoot的主配置类:

img

里面有一个main方法运行了一个run()方法,在run方法中必须要传入一个被@SpringBootApplication注解的类。

@SpringBootApplication

SpringBoot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就会运行这个类的main方法来启动SpringBoot项目。

那@SpringBootApplication注解到底是什么呢,点进去看看:

img

发现@@SpringBootApplication是一个组合注解。

@SpringBootConfiguration

先看看@SpringBootConfiguration注解:

img

这个注解很简单,标明该类是一个Spring的配置类。

再进去看看@Configuration:

img

说明Spring的配置类也是Spring的一个组件。

@EnableAutoConfiguration

这个注解是开启自动配置的功能。

img

先看看@AutoConfigurationPackage注解:

img

这个注解是自动配置包,主要是使用的@Import来给Spring容器中导入一个组件 ,这里导入的是Registrar.class。

来看下这个Registrar:

img

就是通过这个方法获取扫描的包路径,可以debug看看:

在这行代码上打了一个断点:

img

启动项目:

进入断点处:

img

看看能否获取扫描的包路径:

img

已经获取到了包路径:

img

那那个metadata是什么呢:

可以看到是标注在@SpringBootApplication注解上的DemosbApplication,也就是我们的主配置类:

img

说白了就是将主配置类(即@SpringBootApplication标注的类)的所在包及子包里面所有组件扫描加载到Spring容器。所以包名一定要注意。

现在包扫描路径获取到了,那具体加载哪些组件呢,看看下面这个注解。@Import({AutoConfigurationImportSelector.class})

img

@Import注解就是给Spring容器中导入一些组件,这里传入了一个组件的选择器:AutoConfigurationImportSelector。

里面有一个selectImports方法,将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中。

img

debug运行看看:

会给容器中导入非常多的自动配置类(xxxAutoConfiguration);就是给容器中导入这个场景需要的所有组件,并配置好这些组件:

img

img

有了自动配置类,免去了我们手动编写配置注入功能组件等的工作。

那他是如何获取到这些配置类的呢,看看上面这个方法:

img

img

img

会从META-INF/spring.factories中获取资源,然后通过Properties加载资源:

imgSpring Boot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值,将这些值作为自动配置类导入到容器中,自动配置类就生效,帮我们进行自动配置工作。以前我们需要自己配置的东西,自动配置类都帮我们完成了。

img

J2EE的整体整合解决方案和自动配置都在spring-boot-autoconfigure-2.0.3.RELEASE.jar:

img

img

比如看看WebMvcAutoConfiguration:

都已经帮我们配置好了,我们不用再单独配置了:

img

三、spring-cloud相关

1.Dobbu是怎么配置的?超时时间多少,重试次数多少?
2.Dobbu的实现原理是什么?
3.nginx的负载均衡怎么配的?
4.es中怎么使用索引?
5.跨域问题怎么解决?

6.redis和zk分布式锁的区别

7.布隆过滤器怎么配的?
8.熔断器怎么做限流?
9.es版本之间的区别?每个版本之间区别很大
10.springCloud里有那些组件?
11.redis里放了那些数据?

12、数据库用户模块使用了redis来解决重复注册的问题,如何用关系型数据库解决?

13.Redis除了RedisTemplate,还有什么可以操作?

14.Redis解缓存雪崩、缓存击穿、缓存穿透的产生原理以及解决
方案

15、redis内存利用率多少

16、他还问服务器的配置多少核多少内存多少cpu,压测的时候系统水位多少、cpu利用率多少,内存利用率多少

17、redis分布式锁的那个方法怎么实现的?vue简单唠一唠

18.dubbo的流程
19.eureka怎么注册,怎么维护和调用

20.springcloud组件,Hystrix怎么用
21.zuul网关什么情况下进行回调

22、Eureka 有什么作用
23、Eureka 和 Zookeeper 的区别
24、Redis 和 Zookeeper 创建分布式锁的过程 有什么区别

25、我写了参与部分技术方案的编写 他问了 一下 我描述了 后端的微服务架构 他有问到RPC 和 HTTP 的区别 为什么这么用

26.缓存雪崩 缓存击穿 缓存穿透 解决方案

27、项目中zk的应用

28.redis平时在哪里用到
29.redis怎么设置过期时间
30.redis数据类型

  1. 缓存雪崩和缓存击穿的解决方案;
  2. 假如在处理redis里的消息时出问题,redis的内存被打满是什么怎么处理的

四、秒杀库相关

1、秒杀业务要理顺,最好结合下单业务一起整理思路。这个过程面试官会穿插问一些用到的技术栈,redis,MQ,zk等相关问题都可能问到。分布式锁、幂等性等与秒杀直接相关的技术一定要了解。
2、一般会有编码题,这个大部分是操作字符串得到指定的结果。比如:在一个字符串中,得到第一个不重复的字符。可以到网上找一些有关问题做一下。
3、ThreadLocal在项目中用在那个地方,有什么作用
4、CurrentHashMap原理
5、==和equals
6、为什么服务内用rpc,服务外用http
别的我忘记了,都是基础的面试题。还要说一下,对于我面试的情况,问项目、面试题和编码题的时间是4:3:1,所以一定把秒杀讲清楚了,甚至redis\MQ中储存了什么具体数据,越细越好。

7、秒杀的购物车是放redis里的?不是秒杀的购物车,正常情况下的购物车放在哪里操作执行?项目里面有多少个微服务?
数据库里怎么保证超卖,少卖?

8、面试被问到秒杀怎么开启,秒杀订单创建成功但没有付款的情况怎么办

9、面试被问到秒杀怎么开启,秒杀订单创建成功但没有付款的情况怎么办

10.下订单到结款的具体流程

11.问到秒杀项目,会根据项目 问你一些设计 思想 有没有扩展的空间 中间开发过程中有没有什么问题 问的非常灵活 我回答的很勉强 感觉项目这块 还是比较麻烦

例子:汉克 二面(50分钟 基础知识基本没有偏离课上内容 项目只问了秒杀 回答的较差 )

12.秒杀业务中怎么保证数据库的安全 超卖 少卖的解决方案

13.统计订阅人数:
同时有N个商品(用long类型的商品id表示),每个商品都可以被任何一个用户(long类型的用户id)订阅,每被订阅一次,该商品的订阅数加1,同一个用户对同一个商品只能订阅一次 编辑写一个类,用3个方法提供以下功能(这3个方法都是在单机多线程环境下调用):

  1. 为指定的用户id订阅指定的商品id
    2.返回所有商品的订阅总数

3.根据商品ID返回这个商品的订阅数

14.解决超卖少卖问题为什么不用分布式锁

15.限流怎么实现的,
16、重复下单问题,

五、集合相关

1.hashmap的核心源码,为什么是超过8变成红黑树,小于6变成链表。为什么是8和6

jdk1.8后,hashmap存储数据使用的数据格式:数组+链表+红黑树**

**使用了尾插法:先插入、再扩容**

**链表长度大于等于8的时候->红黑树,<=6的时候->链表**

**为什么是8?因为根据离散分析,8的节点是对于系统的查询性能和红黑树自旋造成的开销,这两个因素之间的权衡性来说,是最好的。**

**为什么要等到<=6才转换链表,在8和6中间,给一个缓冲7,防止7和8之间频繁变换造成红黑树和链表的频繁转换**

2.红黑树

红黑树也是二叉查找树,红黑树是自平衡的二叉查找树,在进行插入和删除等可能会破坏树的平衡的操作时,需要重新自处理达到平衡状态。**

**红黑树能自平衡,它靠的是三种操作:左旋、右旋和变色。**

**左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。**

**右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。**

**变色:结点的颜色由红变黑或由黑变红。**

**红黑树的时间复杂度为Olog(n)。**

**2、红黑树,满足以下全部性质的树,我们才称之为红黑树:**
**1)每个结点要么是红的,要么是黑的。**
**2)根结点是黑的。**
**3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。**
**4)如果一个结点是红的,那么它的俩个儿子都是黑的。**

**5)对于每个结点,从该结点到其所有子孙叶结点的路径中所包含的黑色结点数量必须相同。**

3.hashmap的底层原理,hash如何实现;

**HashMap是基于数组+链表实现的。**

**put时在多线程的情况下,会形成环从而导致死循环。**

**数组长度一般是2n,从0开始编号,所以hashcode & (2n-1),(2n-1)每一位都是1,这样会让散列均匀。需要注意的是,HashMap在JDK1.8时引入了红黑树结构进行优化,当链表元素个数>=8时,链表将转化为树结构;若桶中链表元素个数<=6时,树结构还原成链表。因为红黑树的平均查找时间复杂度是lgN,长度为8的时候,平均查找长度为3,如果继续使用链表,平均查找长度为8/2=4,这才有转换为树的必要。链表长度如果<=6,6/2=3,虽然速度也很快,但是转换为树结构和生成树的时间并不会太短。还有选择6和8,中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表元素个数超过8则链表转换为树结构,链表元素个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁地发生链表转树,树转链表,效率就会很低。**

4.(上一个问题引申)linked list如何(在不破坏链表结构下)删除某个节点上的数据。

5.ConcurrentHashMap和hashtable线程安全有什么区别

**Hashtable:锁整个Map,对get也上锁,所以并发性能很差。**

**ConcurrentHashMap:采用了CAS+同步锁Synchronized对链表头节点进行锁定,底层使用数组+链表+红黑树**

6.说说你对Hashmap的认识(有多少说多少,尽量把磊哥的那份资料吃透)

7.ConcurrenHashMap 的底层进而介绍sychronized 和 CAS

**ConcurrentHashMap:**

- **1.7 segment对象来上锁:用segment上锁,锁的粒度变小,但是锁的粒度还不够小,在同一个segment中的entry依然是需要进行锁的竞争,影响并发性**

- **1.8 cas+synchronized上锁:内部大量使用了cas+synchronized上锁进行上锁,实现锁的粒度和锁的并发性是较为均衡的(synchronized锁的膨胀升级)。1.8ConcurrentHashMap几乎跟HashMap性能一样了。**

- **CAS如何上锁:compareAndSwapObject**

  **CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。**

  **更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。**

  **CAS的缺点:**

  **1) CPU开销过大**

  **在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。**

  **2) 不能保证代码块的原子性**

  **CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。**

  **3) ABA问题**

  **解决**:

  **添加一个版本号,0比较变量的版本号是否一致。**

8,hashMap底层数据结构及实现原理

**HashMap是无序且不安全的数据结构。**

**HashMap 是以key–value对的形式存储的,key值是唯一的(可以为null),一个key只能对应着一个value,但是value是可以重复的。**

**HashMap 如果再次添加相同的key值,它会覆盖key值所对应的内容,这也是与HashSet不同的一点,Set通过add添加相同的对象,不会再添加到Set中去。**

**HashMap 提供了get方法,通过key值取对应的value值,但是HashSet只能通过迭代器Iterator来遍历数据,找对象。**

**jdk8中添加了红黑树,当链表长度大于等于8的时候链表会变成红黑树链表新节点插入链表的顺序不同(jdk7是插入头结点,jdk8因为要把链表变为红 黑树所以采用插入尾节点)hash算法简化 ( jdk8 )resize的逻辑修改(jdk7会出现死循环,jdk8不会)**

9,hashMap和hashTable的区别,又引入了ConCurrentHashMap和hashTble的区别

**HashMap底层没有多线程的机制,所以适用于单线程,HashTable适用于多线程机制,所以在多线程条件下HashTable是线程安全的。 这个再啰嗦一句,Collections虽然呐是提供了一个方法来为HashMap实现多线程操作的,但是那个方法在一定情况下是不安全的。它只是部分方法同步,但是整体的方法不同步。 (2)从null的键值上来说,HashMap键中只允许有一个为null,但是值却是可以多个为null,所以判断为空时get()方式得到的,可能是键为null,可能是值为null,应该用containsKey()方法来判断。**

**然后再说一下虽然HashTable可以实现同步但是效率低,它是把整个hash表都给我锁住了,有没有一种方式效率高些,既实现了同步,又效率高呐。**
**就是ConcurrentHashMap了,这个是在jdk5推出的,它的方式,相比于HashTable锁住了整张hash表,一次只能又一个线程操作,1.ConcurrentHashMap是将hash表分成16个桶,它的锁机制只是锁住一个桶,所以这16个桶可以同时操作效率明显提升。1.8以后,ConcurrentHashMap又取消了分段锁的算法,将底层实现也变成了CAS,CAS也就成了无锁算法的代名词了**

10.hashmap为什么使用红黑树不使用别的树,消息队列的机制,在开发规范中你觉得对你而言比较重要的是什么,对单一抽象层次怎么理解的,arraylist,描述下最近做的项目,秒杀业务

**1、AVL树和红黑树有几点比较和区别:**
**(1)AVL树是更加严格的平衡,因此可以提供更快的查找速度,一般读取查找密集型任务,适用AVL树。**
**(2)红黑树更适合于插入修改密集型任务。**
**(3)通常,AVL树的旋转比红黑树的旋转更加难以平衡和调试。**

**(1)AVL以及红黑树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。**
**(2)两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。**
**(3)在AVL树中,从根到任何叶子的最短路径和最长路径之间的差异最多为1。在红黑树中,差异可以是2倍。**
**(4)两个都给O(log n)查找,但平衡AVL树可能需要O(log n)旋转,而红黑树将需要最多两次旋转使其达到平衡(尽管可能需要检查O(log n)节点以确定旋转的位置)。旋转本身是O(1)操作,因为你只是移动指针。**

**2、消息队列可以对同步的通信结构进行改造,改成异步的通信结构。使用异步的通信结构可以极大的提升系统的吞吐量,因此消息队列解决的是通信问题。**

11.集合有哪些,hashset如何保证存放的对象不重复

 **Java集合类型主要有3种:set(集)、list(列表)和map(映射)。**

**Collection、Set和List的区别如下:**
**Collection对象之间没有指定的顺序,允许有重复元素和多个null元素对象;它是Set和List接口的父类,是一种最通用型的集合接口;**

**Set各个元素对象之间没有指定的顺序,不允许有重复元素,最多允许有一个null元素对象;**
**List各个元素对象之间有指定的顺序,允许重复元素和多个null元素对象;**  

**HashSet的底层调用的是Map的put方法。而在put方法里面,添加的元素作为键key来存储的。** 
**HashSet保证元素不重复是通过两部分实现的,第一步通过比较两个对象的哈希码是否相同如果相同,只能怀疑是相同对象,那么进而就会调用equals就行二次确认,如果确认完毕之后相同,那么就会排除第二个,否则的话就会插入该元素。因此,如果要保证存入对象的内容不同的时候就需要同时重写hash()和equals()方法自己定义比较的规则,一定要保证相同内容的对象的哈希码是相同的,不同对象的哈希码是不同的。**

12、concurrentHashMap的机制,锁的问题,hashmap数据结构,线程安全问题

13.hashmap处了链表变红黑树有什么其他的设计是为了减少hash碰撞

**1、扰动函数算法,促使元素位置分布均匀,减少碰撞几率;**

**2、使用final对象,并采用合适的equals方法和hashCode方法;**

六、spring相关

1.IOC创建对象的流程>>ioc aop 核心原理
2.循环依赖怎么解决

3.ioc,aop以及使用场景

(别模仿)我昨天面了中软的业务回答的还可以,但是他问spring的时候说我回答的太表面,源码那些我也没答出来,然后聊了40多分钟,结果没过

4、springboot如何使用yml加载指定配置文件,自动装配那个注解具体使用的哪个类,怎么加载的springfactories文件,为什么用mybatis配置文件不用全注解开发

5、对动态代理的理解,动态代理的两种方式,哪些场景用到了动态代理

6.springmvc的注解

7.springBoot核心注解,@SpringBootApplication的组成,自动化配置原理;

8.aop,ioc原理;

9.你在开发中ioc和aop的应用场景。

10.Autowired Resource 区别

七、mysql进阶(高级)相关

1.mysql索引结构
2.为什么使用B+做索引,B树和B+树的区别

3.es和mysql的MyISAM有什么区别

4.主要的SQL优化有哪些,值为null的字段建立索引可以命中吗

5.sql优化

  1. sql优化,索引的数据结构;
  2. sql查找select *与具体字段哪个快
  3. 100w个数中间少一个 快速找出来是哪个
  4. 索引键能不能为空 为什么
  5. mysql和redis的双写一致性
  6. 在你的项目里会不会有缓存穿透的问题
  7. 声明式事务

八、javaSE相关

1.stringbuffer和stringbuilder的区别?

答:stringbuffer线程安全的可变字符序列,从jdk1.5开始为该类补充了一个单个线程使用的等价类,即 StringBuilder;此类提供一个与 `StringBuffer` 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。

- 出现的版本
  - StringBuffer===jdk1.0
  - StringBuilder===1.5
- 线程
  - StringBuffer===安全
  - StringBuilder===不安全
- 效率
  - StringBuffer===相对慢
  - StringBuilder===相对快



笔记一阶段day14.day15。

2.arraylist和linkedlist的区别?

- ArrayList
  - 基于数组实现List接口的集合
  - 增删慢
  - 查询快
- LinkedList
  - 基于链表实现List接口的集合
  - 增删快
  - 查询慢

笔记day16。

3.redis在项目中作用,缓存雪崩

4.继承和实现的区别?

**继承**:如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。

**实现**:如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们实现这个接口,各自实现自己具体的处理方法来处理那个目标 。

上面是这两个概念的一些基本区别;

联系:继承父类和实现接口都能实现代码重用,提高开发效率。体现了实物的传递性,继承关系达到复用的目的。

区别:

1、修饰不同

不同的修饰符修饰;实现:implements,继承:extends;

2、数量不同

Java只支持“接口”的多继承,不支持“类“”的多继承;而继承在java中具有单根性,子类只能继承一个父类。

总结为:单继承,多实现。

3、属性不同

在接口中只能定义全局常量(static final),和无实现的方法;而在继承中可以定义属性方法,变量,常量等...

4、调用不同

某个接口被类实现时,在类中一定要实现接口中的抽象方法;而继承想调用那个方法就调用那个方法。

5.==和equals的区别?

  • “==”对于基本类型来说是比较值,对于应用类型来说是比较引用
  • equal默认情况下是对引用的比较,只是很多类重写了equals方法,比如String、Integer等将它变成了值的比较,所以一般情况下equals是比较值是否相等

6、集合

1、数据结构不同
ArrayList是Array(动态数组)的数据结构,LinkedList是Link(链表)的数据结构。
2、效率不同
当随机访问List(get和set操作)时,ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
当对数据进行增加和删除的操作(add和remove操作)时,LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。
3、自由性不同
ArrayList自由性较低,因为它需要手动的设置固定大小的容量,但是它的使用比较方便,只需要创建,然后添加数据,通过调用下标进行使用;而LinkedList自由性较高,能够动态的随数据量的变化而变化,但是它不便于使用。
4、主要控件开销不同
ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。

7.堆和栈有什么区别,内部结构

1.栈内存存储的是局部变量而堆内存存储的是实体;

2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

详解见保存的网页。

8.垃圾回收发生在哪里

垃圾回收一般发生在堆内存中

详解见保存的网页

9.1.7与1.8的区别,(头插法和尾插法,为什么1.8要这样设计)

拉姆达表达式、另外就是ConcurrentHashMap。区别详见day14秒杀。

10.有哪些集合是有序的,hashmap的实现,是否安全

Collection

- List--有序
- Set--无序
哪些 Map 是有序的?
LinkedHashMap
ConcurrentSkipListMap
TreeMap


HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。

详解见保存的网页以及微信小程序收藏,day14秒杀视频里有详细解释。

九、锁相关

1、NIO和BIO的区别,高并发下用NIO,然后接着问了一个如果一个用户一个线程岂不是更慢?我没回答出来,

Java对BIO、NIO、AIO的支持:

  • Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
  • Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
  • Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,

BIO、NIO、AIO适用场景分析:

  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
  • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
  • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。

资料网址:https://blog.csdn.net/skiof007/article/details/52873421

2、synchronized和lock的区别。

类别 synchronized lock
存在层次 java的关键字,在JVM层面上 是一个类
锁的释放 1.当获取锁的线程执行完同步代码块 2.线程执行发生异常,JVM会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设线程A获得锁,线程B等待,线程A阻塞的话,线程B会一直等待 可尝试获得锁的,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入,不可中断,非公平 可重入,可判断,可公平
性能 少量同步 大量同步

资料网址:https://blog.csdn.net/hefenglian/article/details/82383569

3、redis怎么做分布式锁的。

  • 直接使用分布式锁
    • redisTemplate.opsForValue().setIfAbsent(“device:lock”, 1)
  • 使用Redisson集群上锁
    • 上锁成功的线程:默认的锁的超时时间是30秒(也可自定义)。会开启另一条线程,定时任务:每隔10秒执行一次锁的续约(30秒,也可自定义)
    • 其他线程:while- true循环,反复尝试去获得锁,直到拿锁成功

4、介绍一下dubbo。剩下都是秒杀相关的了,还有mq怎么保证消息投递的

Apache Dubbo |ˈdʌbəʊ| 提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。

dubbo是一款高性能的rpc框架。什么是rpc呢?

rpc是一种协议:是一种远程过程调用(remote procudure call)协议

rpc协议是在应用层之上的协议,规定了通信双方进行通信的数据格式是什么样的,及数据如何传输:

- 指明调用的类或接口
- 指明调用的方法及参数



dubbo如何实现远程通信?

- 服务的消费者去注册中心订阅到服务提供者的信息,然后通过dubbo进行远程调用



dubbo的内部结构

- dubbo提供了一个容器用来存放服务提供者(初始化)
- 服务提供者将服务名、及具体的服务地址、端口等信息注册到注册中心上(初始化)
- 服务消费者订阅需要的服务(初始化)
- 注册中心异步通知服务的变更情况
- 服务消费者同步的调用到服务提供者的服务
- 监控中心实时监控和治理当前的服务

rabbitMQ如何保证消息的可靠性投递?

1.消息持久化

RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。

所以就要对消息进行持久化处理。如何持久化,下面具体说明下:

要想做到消息持久化,必须满足以下三个条件,缺一不可。

1) Exchange 设置持久化

2)Queue 设置持久化

3)Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息

2.ACK确认机制

多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?

这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。

这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。

3.设置集群镜像模式

我们先来介绍下RabbitMQ三种部署模式:

1)单节点模式:最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
2)普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(必须持久化消息情况下)。
3)镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案

下面介绍下三种HA策略模式:

1)同步至所有的
2)同步最多N个机器
3)只同步至符合指定名称的nodes

命令处理HA策略模版:rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]

1)为每个以“rock.wechat”开头的队列设置所有节点的镜像,并且设置为自动同步模式
rabbitmqctl set_policy ha-all “^rock.wechat” ‘{“ha-mode”:“all”,“ha-sync-mode”:“automatic”}’
rabbitmqctl set_policy -p rock ha-all “^rock.wechat” ‘{“ha-mode”:“all”,“ha-sync-mode”:“automatic”}’

2)为每个以“rock.wechat.”开头的队列设置两个节点的镜像,并且设置为自动同步模式
rabbitmqctl set_policy -p rock ha-exacly “^rock.wechat”
‘{“ha-mode”:“exactly”,“ha-params”:2,“ha-sync-mode”:“automatic”}’

3)为每个以“node.”开头的队列分配指定的节点做镜像
rabbitmqctl set_policy ha-nodes “^nodes.”
‘{“ha-mode”:“nodes”,“ha-params”:[“rabbit@nodeA”, “rabbit@nodeB”]}’

但是:HA 镜像队列有一个很大的缺点就是: 系统的吞吐量会有所下降

4.消息补偿机制

为什么还要消息补偿机制呢?难道消息还会丢失,没错,系统是在一个复杂的环境,不要想的太简单了,虽然以上的三种方案,基本可以保证消息的高可用不丢失的问题,

但是作为有追求的程序员来讲,要绝对保证我的系统的稳定性,有一种危机意识。

比如:持久化的消息,保存到硬盘过程中,当前队列节点挂了,存储节点硬盘又坏了,消息丢了,怎么办?

产线网络环境太复杂,所以不知数太多,消息补偿机制需要建立在消息要写入DB日志,发送日志,接受日志,两者的状态必须记录。

然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。

资料网址:https://www.cnblogs.com/flyrock/p/8859203.html

5.乐观锁的使用场景

  • 由于乐观锁的不上锁的特性,适合用在DB读大于写的场景,以及并发竞争不激烈的场景下

6.cas锁

CAS机制使用了三个基本操作数:内存地址V、旧的预期值A、要修改的新值B

更新一个变量时,只有当变量的预期值A和内存地址V的值一致,才会将内存地址V的值修改为要更新的值B

举例:

  • 当线程1带着预期值A=10,去修改内存地址V=10为更新值B=11
  • 此时线程2抢先线程1一步将内存地址V修改为11
  • 线程1开始提交更新,先进行比较预期值A和内存地址V是否相等,发现内存地址V的值已经被修改成更新值B,提交更新失败
  • 线程1重新获取内存地址V的值,并重新计算想要的更新值B,此时A=11,B=12。这个重新尝试的过程被称为自旋
  • 现在没有线程抢占,线程1将内存地址V的值更新成B=12

CAS锁是乐观锁,乐观的认为程序中的并发情况不严重,让线程不断的去更新重试

CAS的缺点

  • CPU开销过大

在并发量较高的情况下,如果许多线程反复尝试更新某个变量,却一直更新失败,循环往复,这样会给CPU带来很大的压力

  • 不能保证代码块的原子性

CAS机制保证的只是一个变量的原子性操作,不能保证一个代码块的原子性操作

  • ABA问题

解决ABA问题

给存储在内存地址V中的变量(A=10)设置一个版本号(修改一次,递增一次),这样即使这个变量的值被其他线程修改后还是当前值(A=10),但是版本号已经发生改变,所以修改还是失败的

7.zookeeper 分布式锁 冗余部署数据一致性问题 和 redis的区别

zookeeper中锁的种类:

​ 读锁:大家都可以读,上锁的前提:前面所有的锁都是读锁

​ 写锁:只有拿到写锁才能写,上锁的前提:前面没有锁

zk中如何上读锁

  • 创建一个临时序号节点,节点数据是read,表示是读锁
  • 获取当前zk中序号比自己小的所有节点
  • 判断最小节点是否是读锁
    • 如果不是读锁,上锁失败,为最小的节点设置监听。zk的watch机制会当最小的节点发生变化时通知当前节点,再执行第二步流程
    • 如果是读锁,上锁成功

zk中如何上写锁

  • 创建一个临时序号节点,节点数据时write,表示是写锁
  • 获取所有的子节点
  • 判断最小的子节点是否上锁
    • 如果没锁,上锁成功
    • 如果有锁,上锁失败。zk的watch监听最小的节点,当发生变化时,通知当前节点,再执行第二步操作

羊群效应

如果使用上述的上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样对zk的压力就会特别大

解决方法

将监听事件修改成链式监听,后一节点只监听前一节点,而不是去监听前面所有节点

冗余部署服务数据一致性问题

使用ZAB协议,这个协议解决了zookeeper的崩溃恢复和主从数据同步问题

zk的分布式锁和redis分布式锁对比

  • redis分布式锁,需要自己不断去尝试获取锁,比较消耗性能
  • zk分布式锁,获取不到锁,注册监听器,不需要不断主动去获取锁,性能开销较小
  • 假如redis获取锁的客户端挂了,那就只能锁超时后才能释放锁;而zk因为创建的临时节点,如果客户端挂了,znode就没了,此时会自动释放锁

十、jvm相关

1.超类object有哪些方法;
2.介绍下JVM,各个区域,程序计数器用途,哪一个区域是线程独享的哪一块区域是线程共有的;
3.分布式事务,描述下TCC模式(AT,SAGA,XA);

4、jvm原理两个gc区别

5.jvm本地方法栈

6.怎么预防死锁

7.JVM堆中存什么,栈中存什么

8.JVM栈帧
9.类加载器有哪些,双亲委派的好处
10.垃圾回收算法

11.说说你对JVM 的了解有哪些
我回答了 JVM内存结构之后说了上课的哪些
又问了GC什么哪几种

12.jvm调优 用了哪些jdk工具 类加载器 gc 什么会被垃圾回收

11、docker-Linux相关

1.docker配置有了解嘛

2.Linux的命令,比如查看内存是否占用,删除文件,修改文件

12、并发相关

面试官问我压测的时候多少,我说一万多,又问我限流多少,我说10万,那他说你这限流没啥用啊,然后问我qps多少,我说峰值八千多,均值2.3左右,他又问,你这才八千对于redis来说高了还是低了,我说低了,redis能撑10万,他说那你还把结束标记存jvm缓冲,还要配zookeeper,不麻烦吗,我说并发不高的时候可以放redis,这样设计为了大流量进来更能提升性能,他又怼说我你压测一万多还大流量。我是不是压测说低了

13、笔试题目相关

1、//评测题目: 有两个字符串A和B,请输出这两个字符串倒序后的和。例如
// A: 652
// B: 123
// A倒序后为256,B倒序后为321,所以输出数值为256+321=577。
// 请注意输入为字符串,输出为数值。请注意处理所有输入情况。

2、我那两道是:1.输出数组中占比超过一半的单个数字,如果没有就输出-1;2.合并两个有序数组让合并后数组依旧有序(不让合并之后用sort方法)

3、蓝川科技笔试题:工厂模式,单例模式,快速排序,复杂sql,jquery,的伪代码手写。面试题:对哪个框架理解最深,介绍一下原理,项目介绍了一下然后讲了下消息积压的解决,

14.图片资料

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RM1qVN7H-1647064816823)(C:\Users\圆圆\AppData\Roaming\Typora\typora-user-images\1629436112802.png)]

15、面试实时资料

1.武汉佰钧成,一面基础jvm,内存结构,垃圾回收机制,二面项目介绍秒杀,库存为什么放到redis,redis的数据结构,库存用的哪种,为什么用hash不用string,redis并发为什么好,分布式事务怎么保证库存扣减一定成功,幂等性怎么保证的,线程池的执行原理,答了课上说的又问了更底层的原理知不知道,我说不知道,项目中拿到数据怎么进行数据库的建表和数据写入,sql效率怎么查看,慢sql怎么查看,dubbo和zk这一套的原理,底层netty和nio的原理,为什么nio相比io效率更快,三面场景spring的理解,aop怎么用的,动态代理问的很细,方法a里面调用方法b两种场景:a方法有@trasanctional注解b方法没有,a方法有这个注解b方法没有,问两种情况分别有没有事务

2.晚上阿里面试:java的锁的区别,redis雪崩,击穿;zk的锁;hashmap;mysql的默认隔离级别;乐观锁,悲观锁

3.阿里外包二面:主要还是线程池,和一些基本的排序算法吧,然后其他就是现实开发大概的流程,如技术方案怎么写,谁写,一个需求来了,到交付大概的流程,

4.共道科技一面:sql优化,kafka细节,spring,springboot,springcloud,fegin,eureka,docker和dockercompose基本命令,项目发布的流程,线上出问题咋办,gitlab工作流,项目有几种环境

5.视频面,秒杀中redis上分布式锁作为排队标记是怎么实现的,mysql优化索引怎么建,有什么要求,abc三个字段怎么匹配最左前缀法则,一个场景:在mysql中ab两张表字段一样,a表是空的没有数据,b表有数据,怎么写sql吧b表的数据写到a表,一条sql能不能实现,其他没了,都是技术之外的为题

6.秒杀:画时序图
访问秒杀结束标记,还判断库存吗,还是原子操作吗,逻辑是怎样的!!
分布式锁为什么不一开始在访问就使用,setnx具体作用,
访问库存的流程,有job吗,redis和mysql数据同步怎么实现,
为什么要使用zk?
下单时依然出现超卖要怎么解决,如何保证mysql数据,可以用乐观锁来解决吗,
sql索引失效的情况 ,原理是什么,
kafka出现消息积压怎么解决,怎么保证消费可靠性,
手写一个requestmapping,加载url原理。

7.华萃面试,mysql优化建索引时对字段有什么要求,什么类型的字段适合建索引,在哪些场景中用到kafka,redis原子操作解决超卖少买是具体是怎么实现的,索引失效的情况

8.在mysql中插入多条数据,但是其中一个数据发现错了,问怎么回滚,还有安全框架security中怎么根据不同的权限显示不同的页面,是怎么实现的

16、其他

1、最左前缀法则

2、volatile的工作内存,主内存

3.项目怎么打包怎么运行

4.项目具体数据量多大,日志多大

5.你在之前的开发中有什么让你印象深刻的问题

6.项目有多少人

7.有一个问题 大家思考下,面试也常问:你项目中最近碰到的一个问题,怎么解决?——答案可以去晨考里找,或者把秒杀里碰到的问题拿出来说

9.这个项目在你们公司是怎么推进的 项目的服务划分 通过业务

产品经理提需求,然后预定一个会议室,把我们的产品经理、技术人员(前、后、部门技术负责人)都喊上,然后开一个需求的评审会,在这个评审会上会吧整个业务的需求梳理清楚,如果有些地方敲不定,就开到敲定为止,然后前、后、ui出技术方案,然后进行UI的技术评审、前端的技术评审、后端的技术评审(工期、排期)在开发中可能出现的问题,并且怎么解决,然后根据接口文档来进行开发,开发完之后,先自测,然后申请资源测试,然后开发布评审会,确定这个版本发布的细节,然后进行一个灰度测试,然后就是发布。

10.kafka的事务性

就是分布式事务,就是

第一阶段预留资源

第二阶段:如果第一阶段出现问题,那么就进入取消阶段

第三阶段:如果第一阶段成功,那么就进入确认阶段

11.Spring创建Bean的方式

Spring提供了4中定义Bean的方式,1.注解,2.xml,3.javaConfig,4.基于Groovy DSL配置,一般都是用注解比较多,当然,具体的还得看代码风格,反正就是定义元数据,给到Spring解析

12.Spring的Aop的理解

SpringAop解决的是非业务代码抽取的问题,Aop底层的技术是动态代理,在Spring内实现依赖的是BeanPostProcessor(如果我们想在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IoC容器中),其实就是在方法的前后增加非业务代码,然后扯到业务上去,我们在业务上用的就是在数据大屏项目,,在有硬件一直不发心跳,然后在写入日志的时候给他一个增强(前置通知或者后置通知都可以)

13.Spring的IOC的执行流程

执行的第一件事,就是要加载ApplicationContext对象,这个ApplicationContext是一个接口,并且是BeanFactory的子接口,BeanFactory就是存放与bean相关数据的一个容器,所以说初始化ApplicationContext就是初始化BeanFactory,与Bean相关的操作都是基于ApplicationContext来执行的,或者准确来说并不是由ApplicationContext亲自执行的,它内部还有一个beanFactory属性,这个beanFactory最终会被赋值为一个DefaultListableBeanFactory对象,那其实所有的bean操作都是基于这个属性完成的。

在创建好BeanFactory后,之后就要会对配置文件进行解析,每一个bean标签都会解析成一个BeanDefinition对象,然后注册到BeanFactory中,注册的方式是基于一个ConcurrentHashMap,key值为bean标签的名,value就是这个BeanDefinition对象。

注册完了之后,接下来就是对bean进行实例化了,实例化的过程分两种情况,如果对应的类使用了动态代理,那么就要实例化它对应的Proxy代理类,若没有使用动态代理,则通过反射进行创建。创建好的实例同样是注册在beanFactory中的一个ConcurrentHashMap中。至此ApplicationContext的创建就算是完成了。

后续调用getBean方法,其实就是从ApplicationContext的beanFactory属性中的ConcurrentHashMap进行获取。

这就是IOC执行的一个流程。

14.ThreadLocal的作用

它提供了线程的局部变量,让每个线程都可以通过set、get来对这个局部变量进行操作,从而不会和其他的线程的局部变量进行冲突,实现了线程的数据隔离

15.dubbo和SpringCloud的区别

1.最大的区别:
Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式。严格来说,这两种方式各有优劣。虽然在一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适。

16.HashMap的put原理

HashMap在put方法中,它使用hashCode()和equals()方法。当我们通过传递 key-value对调用put方法的时候,HashMap使用Key hashCode()和哈希算法 来找出存储key-value对的索引。如果索引处为空,则直接插入到对应的数组 中,如果索引处不为空,则进行覆盖,否则,判断是否是红黑树,若是,则 红黑树插入,否则遍历链表,若长度不小于8,则将链表转为红黑树,转成功 之后 再插入。

17.SpringBoot的核心注解

@SpringBootApplication为SpringBoot核心启动类上的注解,点进去可知其由三个注解组合而成。 这三个注解分别是: @SpringBootConfiguration 该注解由Spring原生注解@Configuraction封装而成。带有@Configuration的类,可以用来代替 applicationContext.xml 配置文件,而配置文件内部的数据注入,可通过其内部的几个注解-如下,进行实现: 1、@Bean 用来代替 XML 配置文件里面的bean配置。 2、@ImportResource 如果有些通过类的注册方式配置不了的,可以通过这个注解引入额外的 XML 配置文件,有些老的配置文件无法通过 @Configuration 方式配置的非常管用。 3、@Import 用来引入额外的一个或者多个 @Configuration 修饰的配置文件类。 @ComponentScan 该注解主要用来开启组件扫描,可以扫描该注解标注的类、以及该注解标注的类的目录路径下的类,并将其实例注册添加到Spring容器中。 @EnableAutoConfiguration该注解主要用来提供自动装配,是这三个注解中最重要的一个注解。她是Spring Boot新添加的注解,提供了强大的自动依赖功能

18.synchronized 和Lock区别

Lock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。 lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现; 异常是否释放锁: synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。) 是否响应中断 lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断; 是否知道获取锁 Lock可以通过trylock来知道有没有获取锁,而synchronized不能; Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离) 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。 synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,

你可能感兴趣的:(Java面试题,面试,java,职场和发展)