JAVA高频面试题

1. 字符串常量Java内部加载

String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

String str1 = new StringBuilder("re").append("dis").toString();
System.out.println(str1);
System.out.println(str1.intern());
System.out.println(str1 == str1.intern()); // true

//有一个初始化java字符串,在加载sun.misc.Versino这个类的时候进入常量池
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2);
System.out.println(str2.intern());
System.out.println(str2 == str2.intern()); // false

2. AQS的原理分析

  1. LockSupport:用来创建锁和其他同步类的基本线程阻塞原语。
  • LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根到底,LockSupport调用的Unsafe中的native代码
  • LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程。
  • LockSupport和每个使用它的线程都有一个许可(permit)关联。permit相当于1,0的开关,默认是0,调用一次unpack就加1变成1,调用一次park会消费permit,也就是将1变成0,同时park立即返回。如再调用park会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark会把permit置为1。每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark也不会积累凭证。
public class LockSupportDemo{
	public static void main(String[] args) {
		Thread a = new Thread(()-> {
			try{TimeUnits.SECONDS.sleep(3L);} catch(InterruptedException e) {e.printStackTrace();}
			System.out.println("a线程进入");
			LockSupport.park();
			System.out.println("a线程唤醒");
		}, "a").start();

		Thread a = new Thread(()-> {
			System.out.println("b线程进入");
			LockSupport.uppark(a); // 唤醒a线程
		}, "b").start();
	}
}
  1. AQS(抽象队列同步器)
  • 用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态
  • AQS = state + CLH队列
  1. ReentranLock加锁过程
  • 尝试加锁
  • 加锁失败,线程入队列
  • 线程入队列后,进入阻塞状态
    案例:
public class ASQDemo {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("----A come in");
                try{TimeUnit.MINUTES.sleep(20);}catch(Exception e){e.printStackTrace();}
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }, "A").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("----B come in");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }, "B").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println("----C come in");
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }, "C").start();
    }
}

lock.lock()方法:

public class ReentrantLock implements Lock, java.io.Serializable{
	private final Sync sync;
	abstract static class Sync extends AbstractQueuedSynchronizer{
		abstract void lock();
	}
	public void lock() {
		sync.lock();
	}
	static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1)) // CAS:state状态为0表示没有线程正在执行,可以执行当前线程
                setExclusiveOwnerThread(Thread.currentThread()); // 将当前线程设置为运行的线程
            else
                acquire(1);
        }
    }

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState(); // 获取到state
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) { // 当前线程和正在执行的线程是不是同一个线程
            int nextc = c + acquires; // 可重入锁
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
	static final class Node {......}
	private transient volatile Node head; // 头指针
	private transient volatile Node tail; // 尾指针
	private volatile int state; // 同步状态
	public final void acquire(int arg) {
      if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
          selfInterrupt();
	}
 	protected boolean tryAcquire(int arg) {
 		throw new UnsupportedOperationException();
 	}
 	private Node addWaiter(Node mode) { // 进入阻塞队列
	      Node node = new Node(Thread.currentThread(), mode);
	      Node pred = tail;
	      if (pred != null) { // 队列已经有元素,入队就行了
	          node.prev = pred;
	          if (compareAndSetTail(pred, node)) {
	              pred.next = node;
	              return node;
	          }
	      }
	      enq(node);
	      return node;
    }
    private Node enq(final Node node) { // 初始化队列
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node())) // 自己建一个节点作为头节点
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    
    final boolean acquireQueued(final Node node, int arg) {
      boolean failed = true;
      try {
          boolean interrupted = false;
          for (;;) {
              final Node p = node.predecessor(); // 当前节点的前边节点
              if (p == head && tryAcquire(arg)) {
                  setHead(node);
                  p.next = null; // help GC
                  failed = false;
                  return interrupted;
              }
              if (shouldParkAfterFailedAcquire(p, node) &&
                  parkAndCheckInterrupt()) // 线程会在这里被挂起
                  interrupted = true;
          }
      } finally {
          if (failed) // 取消排队
              cancelAcquire(node);
      }
    }
    private final boolean parkAndCheckInterrupt() { // 真正的入队,被阻塞
        LockSupport.park(this);
        return Thread.interrupted();
    }
    
}

lock.unlock()

public class ReentrantLock implements Lock, java.io.Serializable {
	public void unlock() {
        sync.release(1);
    }
 	protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null); // 释放锁
        }
        setState(c);
        return free;
    }
    
}

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
	public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
	private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 释放队列中的锁
    }
}

3. Spring AOP的执行顺序

@Service
public class CalcServiceImpl implements CalcService{
	@Override
	public int div(int x, int y){
		int result = x / y;
		System.out.println("=======>CalcServiceImpl被调用了,我们的计算结果是:" + result);
		return result;
	}
}

@Aspect
@Component
public class MyAspect{
	@Before("execution(public int cn.zh.spring.aop.CalcServiceImpl.*(..))")
	public void beforeNotify(){
		System.out.println("******@Before我是前置通知MyAspect");
	}
	
	@After("execution(public int cn.zh.spring.aop.CalcServiceImpl.*(..))")
	public void afterNotify(){
		System.out.println("******@After我是后置通知");
	}

	@AfterReturning("execution(public int cn.zh.spring.aop.CalcServiceImpl.*(..))")
	public void afterReturningNotify(){
		System.out.println("******@AfterReturning我是返回后通知");
	}
	
	@AfterThrowing("execution(public int cn.zh.spring.aop.CalcServiceImpl.*(..))")
	public void afterThrowingNotify(){
		System.out.println("******@AfterThrowing我是异常通知");
	}

	@Around("execution(public int cn.zh.spring.aop.CalcServiceImpl.*(..))")
	public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
		Object retValue = null;
		System.out.println("我是环绕通知之前的AAA");
		retValue = proceedingJoinPoint.proceed();
		System.out.println("我是环绕通知之后的BBB");		
	}
}
  1. spring4中的执行顺序
正常情况:
环绕通知之前@Around--->前置通知@Before--->环绕通知之后@Around--->后置通知@After--->返回后通知@AfterReturning

###############################################
我是环绕通知之前AAA
******@Before我是前置通知MyAspect
		  ======>CalcServiceImpl被调用了,我们的计算结果是5
我是环绕通知之后BBB
******@After我是后置通知
******@AfterReturning我是返回后通知
###############################################

异常情况:
环绕通知之前@Around--->前置通知@Before--->后置通知@After--->异常通知@AfterThrowing

###############################################
我是环绕通知之前AAA
******@Before我是前置通知MyAspect
******@After我是后置通知
******@AfterThrowing我是返回后通知
java.lang.AritHmeticException: /by zero
###############################################
  1. spring5中的执行顺序
正常情况:
环绕通知之前@Around--->前置通知@Before-->返回后通知@AfterReturning--->后置通知@After--->环绕通知之后@Around

###############################################
我是环绕通知之前AAA
******@Before我是前置通知MyAspect
		  ======>CalcServiceImpl被调用了,我们的计算结果是5
******@AfterReturning我是返回后通知
******@After我是后置通知
我是环绕通知之后BBB
###############################################

异常情况:
环绕通知之前@Around--->前置通知@Before--->异常通知@AfterThrowing--->后置通知@After

###############################################
我是环绕通知之前AAA
******@Before我是前置通知MyAspect
******@AfterThrowing我是异常后通知
******@After我是后置通知
java.lang.AritHmeticException: /by zero
###############################################

4. Spring的循环依赖

  1. 什么是循环依赖
    多个Bean相互依赖,A->B,B->C,C->A形成闭环
  2. 依赖注入
    通过构造方法注入可能会产生循环依赖
  3. 解决方法
    通过set方法注入并且是singleton就不会有循环依赖问题【三级缓存】
  4. Bean的生命周期
    实例化(通过反射创建对象)
    属性填充(属性值非自动装配)
    初始化(如数据源赋值、校验属性)
    销毁(ioc容器销毁关闭,关闭数据源)
  1. 三级缓存
public class DefaultSingletonBeanRegistry extends Simple AliasRegistry implements SingletonBeanRefistry{
	// 一级缓存:存放经历了完成生命周期的Bean对象
	private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256);
	// 三级缓存:存放可以生成Bean的工厂【工厂是利用lambda表示的】
	private final Map<String,ObjectFactory<?>>  singletonFactories = new HashMap<>(16);
	// 二级缓存:存放早期暴漏出来的Bean对象,Bean的生命周期未结束(属性还未填充)
	private final Map<String,Object> earlySingletonObjects = new HashMap<>(16);
}
  • A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
  • B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。
  • B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中的状态)然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
  1. 为什么一定要用三级缓存?
    一级和二级缓存可以解决循环依赖问题了,但是当我们要利用AOP时就需要三级缓存了。

5. redis的使用场景

  1. string类型:商品编号,订单号采用INCR命令生成,是否喜欢文章
  2. hash类型:购物车
  3. list类型:微信文章订阅公众号
  4. set类型:微信抽奖小程序,微信朋友圈点赞,微信好友关注社交关系,QQ内推可能认识的人
  5. zset类型:根据商品销售对商品进行排序显示,抖音热搜

6.synchronized的底层实现原理

Java虚拟机基于Monitor对象来实现重量级锁
1.Monitor数据结构

ObjectMonitor(){
	_header			= NULL;
	_count			= 0; //	锁的计数器,获取锁时count数值加1,释放锁时count值减1
	_waiters		= 0; //	等待线程数
	_recursions		= 0; // 	锁的重入次数
	_object			= NULL;
	_owner			= NULL; // 指向持有ObjectMonitor对象的线程地址
	_waitSet		= NULL; // 处于wait状态的线程,会被加入到_WaitSet
	_waitSetLock	= 0;
	_Responsible	= NULL;
	_succ			= NULL;
	_cxp			= NULL; // 阻塞在EntryList上的单向线程列表
	FreeNext		= NULL;
	_EntryList		= NULL; // 处于等待锁block状态的线程,会被加入到该列表
	_SpinFreq 		= 0;
	_SpinClock		= 0;
	OwnerIsThread	= 0;
}

2._owner,_WaitSet和_EntryList转换图
请添加图片描述

  • 当多个线程同时访问同步代码块时,首先会进入到EntryList中,然后通过CAS的方式尝试将Monitor中的owner字段设置为当前线程,同时count加1,若发现之前的owner的值就是指向当前线程的,recursions也需要加1。如果CAS尝试获取锁失败,则进入到EntryList中。
  • 当获取锁的线程调用wait()方法,则会将owner设置为null,同时count减1,recursions减1,当前线程加入到WaitSet中,等待被唤醒。
  • 当前线程执行完同步代码块时,则会释放锁,count减1,recursions减1。当recursions的值为0时,说明线程已经释放了锁。

7. SpingBoot的启动流程

SpringBoot启动流程主要分为两大步骤:

第一步:构造一个SpringApplication的实例,完成初始化的工作。初始化的时候会做以下两件事:

(1)创建并初始化ApplicationInitializer,设置到initializers属性中 。
(2)创建并初始化ApplicationListener,设置到listeners属性中 。

第二步:SpringApplication构造完成之后调用run方法,启动SpringApplication。run方法执行的时候会做以下几件事:

(1)创建并初始化计时器StopWatch,用来记录SpringBoot的启动时间 。
(2)创建并初始化监听器SpringApplicationRunListeners,并启动监听,用于监听run方法的执行。
(3)创建并初始化ApplicationArguments,获取run方法传递的args参数。
(4)创建并初始化环境配置ConfigurableEnvironment。封装main方法的参数,写入到 Environment中,发布 ApplicationEnvironmentPreparedEvent(环境事件)。
(5)创建应用程序上下文ApplicationContext。ApplicationContext是Spring中的核心接口和容器,允许容器通过应用程序上下文环境创建、获取、管理bean。

8. Spring底层原理

1、初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中

  • (1)实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象
  • (2)实例化BeanDefinitionReader注解配置读取器,用于对特定注解(如@Service、@Repository)的类进行读取转化成 BeanDefinition 对象,(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等)
  • (3)实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象

2、将配置类的BeanDefinition注册到容器中

  • 解析用户传入的 Spring 配置类,解析成一个 BeanDefinition 然后注册到容器中

3、调用refresh()方法刷新容器

1、prepareRefresh()刷新前的预处理:
2、obtainFreshBeanFactory():获取在容器初始化时创建的BeanFactory:
3、prepareBeanFactory(beanFactory):BeanFactory的预处理工作,向容器中添加一些组件:
4、postProcessBeanFactory(beanFactory):子类重写该方法,可以实现在BeanFactory创建并预处理完成以后做进一步的设置
5、invokeBeanFactoryPostProcessors(beanFactory):在BeanFactory标准初始化之后执行BeanFactoryPostProcessor的方法,即BeanFactory的后置处理器:
6、registerBeanPostProcessors(beanFactory):向容器中注册Bean的后置处理器BeanPostProcessor,它的主要作用是干预Spring初始化bean的流程,从而完成代理、自动注入、循环依赖等功能
7、initMessageSource():初始化MessageSource组件,主要用于做国际化功能,消息绑定与消息解析:
8、initApplicationEventMulticaster():初始化事件派发器,在注册监听器时会用到:
9、onRefresh():留给子容器、子类重写这个方法,在容器刷新的时候可以自定义逻辑
10、registerListeners():注册监听器:将容器中所有的ApplicationListener注册到事件派发器中,并派发之前步骤产生的事件:
11、finishBeanFactoryInitialization(beanFactory):初始化所有剩下的单实例bean,核心方法是preInstantiateSingletons(),会调用getBean()方法创建对象;
12、finishRefresh():发布BeanFactory容器刷新完成事件

9.mybatis工作原理

(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。

(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory。

(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。

(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。

(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。

(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。

(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。

10. SpringMVC工作流程

1. 用户通过浏览器发起 HttpRequest 请求到前端控制器 (DispatcherServlet)。
2. DispatcherServlet 将用户请求发送给处理器映射器 (HandlerMapping)。
3. 处理器映射器 (HandlerMapping)会根据请求,找到负责处理该请求的处理器,并将其封装为处理器执行链 返回 (HandlerExecutionChain) 给 DispatcherServlet
4. DispatcherServlet 会根据 处理器执行链 中的处理器,找到能够执行该处理器的处理器适配器(HandlerAdaptor)    --注,处理器适配器有多个
5. 处理器适配器 (HandlerAdaptoer) 会调用对应的具体的 Controller
6. Controller 将处理结果及要跳转的视图封装到一个对象 ModelAndView 中并将其返回给处理器适配器 (HandlerAdaptor)
7. HandlerAdaptor 直接将 ModelAndView 交给 DispatcherServlet ,至此,业务处理完毕
8. 业务处理完毕后,我们需要将处理结果展示给用户。于是DisptcherServlet 调用 ViewResolver,将 ModelAndView 中的视图名称封装为视图对象
9. ViewResolver 将封装好的视图 (View) 对象返回给 DIspatcherServlet
10. DispatcherServlet 调用视图对象,让其自己 (View) 进行渲染(将模型数据填充至视图中),形成响应对象 (HttpResponse)
11. 前端控制器 (DispatcherServlet) 响应 (HttpResponse) 给浏览器,展示在页面上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值