2021 Java面试题总结(更新中)
目录
一、面试题
1.SpringMVC的流程?
2.SpringMVC怎么设定 重定向 和 转发 的?
3.SpringMVC常用的注解有哪些?
4.Equal 和 == 区别
==运算符的使用
equals()方法的使用
5.synchronized 和 lock 的异同
相同点:
不同点:
6.多线程中sleep()和wait()的异同
相同点:
不同点:
7.创建多线程的方式
(1)继承Thread类
(2)实现Runnable接口
(3)实现Callable接口
(4)创建线程池
8.线程安全问题
(1)Runnable的线程安全问题
(2)Thread类的线程安全问题
9.同步 异步 与 阻塞 非阻塞
(1)同步和异步
(2)阻塞和非阻塞
10.线程的 run()和 start()有什么区别?
11.线程有哪几种状态
12.notify()和 notifyAll()有什么区别?
13.在 java 程序中怎么保证多线程的运行安全?
14. runnable 和 callable 有什么区别?
15.悲观锁和乐观锁
16.线程池中 submit()和 execute()方法有什么区别?
17.多线程锁的升级原理是什么?
18.String、StringBuffer和StringBuilder的区别
19.三次握手和四次挥手
20.聚合函数
21.redis数据类型
22.redis数据持久化(RDB和AOF)
23.hashmap的put方法的原理
二、每日练习(一天三题)
1.JVM中内存结构模型。
2.Java中多线程在项目中哪里用到了?
3.活锁死锁问题分析,什么是活锁,什么是死锁?出现的原因是什么?如何避免?
1.什么是死锁
2.什么是活锁
4.多线程下如何做到数据共享?(可拓展数据共享的使用场景)
5.多线程下如何保证线程安全?(可拓展两种锁)
6.HashMap的底层详细介绍下?(1.7和1.8前后区别)
7.ArrayList和LinkdList的区别?(使用场景和特性分析)
8.ConCurrentHashMap和HashMap的区别,分段锁机制?(理论)
9.HashMap和Hashtable的区别?
10.NIO和AIO、BIO三者区别?(理论)
11.介绍下Spring框架。(框架简介、框架核心、框架特性等)
框架简介
框架核心
框架特点:
Spring的优点
12.介绍下Spring框架IOC和AOP的实现原理。(理论)
IOC(Inverse of Control):控制反转,也可以称为依赖倒置。
AOP:即面向切面编程
13.Spring框架中beanFactory和FactoryBean有什么区别?
14.SpringBoot框架的项目启动流程?
15.SpringBoot如何实现自动装配。
16.阐述下security框架的授权以及认证流程(带入项目中角色管理模块的实现思路,来阐述此题)
17.Redis的常见数据类型有哪些?(带入具体类型具体使用场景)
字符串string:
列表list:
散列hash:
集合set:
有序集合sorted set:
最后,还有个对key的通用操作,所有的数据类型都可以使用的
18.Redis的缓存雪崩和击穿如何解决,穿透问题遇到过吗?(带入具体的项目场景)
缓存雪崩
缓存击穿
缓存穿透
19.数据库中有2000w条数据,而Redis当中只有20w条数据,如何保证Redis中的数据一定是热点数据?(可考虑设置缓存过期时间)
20.Spring框架中出现的ABA问题(循环依赖)如何解决?(原理,缓存思路)
21.Spring框架中Bean对象的生命周期。(原理)
22.SpringMVC框架的工作原理(带入项目中功能请求来阐述,例如登录请求、查询请求)
23.SpringMVC框架从发起请求到接收请求发生了什么?(网络相关)
24.MyBatis框架如何处理sql注入问题?(MyBatis中#和$符号区别)
25.MyBatis框架有几级缓存,默认开启几级?(理论性)
26.MySql数据库中索引的作用和优缺点?(理论内容)
作用:
优点:
缺点:
27.MySql中索引有几种,索引怎么使用?(带入具体的项目场景)
索引分类
索引的使用
28.什么是事务?事务的特性?MySql中事务的隔离级别?(理论内容)
什么是事务?
事务的特性
MySql中事务的隔离等级?
什么是脏读?什么是不可重复读?什么是幻读?什么是可重复读?
29.内存溢出是怎么回事?内存溢出和内存泄露的区别?
内存泄露
内存溢出
区别
30.项目中哪里使用多线程,哪里使用锁机制了?
三、应用
1.设计模式
2.单点登录
一、面试题
1.SpringMVC的流程?
- 用户发送请求至前端控制器DispatcherServiet;
- 前端控制器收到请求后,调用处理器映射器HandlerMapping,请求获取Handler;
- 处理器映射器根据请求URL找到具体的处理器Handler,生成处理器对象及处理器拦截器(如果有则生成),一并返回给前端控制器;
- 前端控制器调用处理器适配器HandlerAdapter,请求执行Handler;
- 处理器适配器经过适配调用具体处理器进行处理业务逻辑;
- 处理器执行完成返回ModelAndView;
- 处理器适配器将处理器结果ModelAndView返回给前端控制器;
- 前端控制器将ModelAndView传给视图解析器ViewResolver进行解析;
- 视图解析器解析后返回具体视图View;
- 前端控制器对View进行渲染视图(即将模型数据填充至视图中);
- 前端控制器响应用户。
2.SpringMVC怎么设定 重定向 和 转发 的?
- 重定向:在返回值前面加“redirect”,譬如“redirect:http://www.baidu.com”
- 转发:在返回值前面加“forward”,譬如“forward:user.do?name=method4”
转发与重定向的区别
1.地址栏
转发:不变,不会显示出转向的地址
重定向:会显示转向之后的地址2.请求
转发:一次请求
重定向:至少提交了两次请求3.数据
转发:对request对象的信息不会丢失,因此可以在多个页面交互过程中实现请求数据的共享
重定向:request信息将丢失4.原理
转发:是在服务器内部控制权的转移,是由服务器区请求,客户端并不知道是怎样转移的,因此客户端浏览器的地址不会显示出转向的地址。
重定向:是服务器告诉了客户端要转向哪个地址,客户端再自己去请求转向的地址,因此会显示转向后的地址,也可以理解浏览器至少进行了两次的访问请求。
3.SpringMVC常用的注解有哪些?
- @RequestMapping:用于处理请求url映射的注解,可用于类或方法上,用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
- @Controller:用于标记在一个类上,使用它标记的类就是一个SpringMVC Controller 对象。分发处理器将会扫描使用了该注解的类的方法,并检测该方法是否使用了@RequestMapping 注解。@Controller 只是定义了一个控制器类,而使用@RequestMapping 注解的方法才是真正处理请求的处理器。
@Controller 标记在一个类上还不能真正意义上的说它就是SpringMVC 的一个控制器类,因为这个时候Spring 还不认识它。这个时候就需要我们把这个控制器类交给Spring 来管理。有两种方式可以管理:<!--方式一--> <bean class="com.cqvie.handler.HelloWorld"/> <!--方式二--> < context:component-scan base-package = "com.cqvie" /> <!-- 路径写到controller的上一层 -->
@PathVariable:用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。
@Resource和@Autowired:都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
- @RequestBody:注解实现接收http请求的json数据,通过添加导入的jason或其他依赖将json转换为java对象。
- @ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户。
4.Equal 和 == 区别
==运算符的使用
(1)可以使用在基本数据类型变量和引用数据类型变量中 。
(2)如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
(3)如果比较的是引用数据类型变量:比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
(4)补充:== 符号使用时,必须保证符号左右两边的变量类型一致。
equals()方法的使用
(1)是一个方法,而非运算符
(2)只能适用于引用数据类型。
(3)Object类中equals()的定义:
public boolean equals(Object obj){return (this == obj);
}
说明:Object类中定义的equals()和==的作用是相同的,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体。
(4)像String、Date、File、包装类等都重写了Object类中的equals()方法。
不是比较两个引用的地址是否相同,而是比较两个对象的“实体内容”是否相同。
(5)通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写。
重写的原则:比较两个对象的实体内容是否相同。
5.synchronized 和 lock 的异同
相同点:
都可以解决线程安全问题
不同点:
首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器;lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock());
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized有代码块锁和方法锁,lock只有代码块锁;
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可);
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
- 使用lock锁,JVM可以花费更少时间调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
6.多线程中sleep()和wait()的异同
相同点:
- 一旦执行方法,都可以使得当前线程进入阻塞状态
不同点:
- sleep()申明在Thread类中,wait()申明在Object类中;
- 调用要求不同:sleep()可以在任何需要场景下调用;wait()必须使用在同步代码块或同步方法中。
- 如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放同步监视器(锁),而wait()会。
7.创建多线程的方式
(1)继承Thread类
- 创建一个继承于Thread类的子类
- 重写Thread的run()方法 ——> 将此线程的方法声明在run()中
- 创建Thread类的子对象
- 通过此对象调用start()
class MyThread extends Thread{//重写Thread类的run()@Overridepublic void run() {for(int i = 1;i < 100;i++){if(i % 2 == 0){System.out.println(i);}}}
}public class ThreadTest {public static void main(String[] args) {//3.创建Thread类的子对象MyThread t1 = new MyThread();//4.通过此对象调用start():①启动当前线程 ②调用当前线程的run()t1.start();//如下操作仍在main线程中执行的for(int i = 1;i < 100;i++){if(i % 2 == 0){System.out.println(i + "***main()***");}}}
}
(2)实现Runnable接口
- 创建一个实现了Runnable接口得类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
class MThread implements Runnable{//2.实现类去实现Runnable中的抽象方法:run()@Overridepublic void run() {for(int i = 0;i < 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}public class ThreadTest1 {public static void main(String[] args) {//3.创建实现类的对象MThread m1 = new MThread();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象Thread t1 = new Thread(m1);//5.通过Thread类的对象调用start():①启动线程 ②调用当前线程的run() --> 调用了Runnable类型的target的run()t1.start();//再启动一个线程,遍历100以内的偶数Thread t2 = new Thread(m1);t2.setName("线程2");t2.start();}
}
(3)实现Callable接口
- 创建一个实现Callable的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable接口实现类的对象
- 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
- 获取Callable中call方法的返回值,get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
class NumThread implements Callable{@Overridepublic Object call() throws Exception {int sum = 0;for(int i = 1;i <= 100;i++){if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}public class ThreadNew {public static void main(String[] args) {NumThread numThread = new NumThread();FutureTask futureTask = new FutureTask(numThread);new Thread(futureTask).start();try {Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}
(4)创建线程池
newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置时间超过60秒,则被终止并移除缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。
private static void createCachedThreadPool() {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {final int index = i;executorService.execute(() -> {// 获取线程名称,默认格式:pool-1-thread-1System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);// 等待2秒sleep(2000);});} }
因为SynchronousQueue队列不保持它们,直接提交给线程,相当于队列大小为0,而最大线程数为Integer.MAX_VALUE,所以线程不足时,会一直创建新线程,等到线程空闲时,又有60秒存活时间,从而实现了一个可缓存的线程池。
newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动线程数目,将在工作队列中等待空闲线程出现;如果工作线程退出,将会有新的工作线程被创建,以补足指定数目nThreads。
private static void createFixedThreadPool() {ExecutorService executorService = Executors.newFixedThreadPool(3);for (int i = 0; i < 10; i++) {final int index = i;executorService.execute(() -> {// 获取线程名称,默认格式:pool-1-thread-1System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);// 等待2秒sleep(2000);});} }
因为核心线程数与最大线程数相同,所以线程池的线程数是固定的,而且没有限制队列的大小,所以多余的任务均会被放到队列排队,从而实现一个固定大小,可控制并发数量的线程池。
newSingleThreadExecutor(),它的特点在于工作线程数目限制为1,操作一个无界的工作队列,所以它保证了所有的任务都是被顺序执行,最多会有一个任务处于活动状态,并且不予许使用者改动线程池实例,因此可以避免改变线程数目。
private static void createSingleThreadPool() {ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int index = i;executorService.execute(() -> {// 获取线程名称,默认格式:pool-1-thread-1System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);// 等待2秒sleep(2000);});} }
因为核心线程数与最大线程数相同,均为1,所以线程池的线程数是固定的1个,而且没有限制队列的大小,所以多余的任务均会被放到队列排队,从而实现一个单线程按指定顺序执行的线程池。
newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
private static void createScheduledThreadPool() {ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);System.out.println(DateUtil.now() + " 提交任务");for (int i = 0; i < 10; i++) {final int index = i;executorService.schedule(() -> {// 获取线程名称,默认格式:pool-1-thread-1System.out.println(DateUtil.now() + " " + Thread.currentThread().getName() + " " + index);// 等待2秒sleep(2000);}, 3, TimeUnit.SECONDS);} }
因为使用了延迟队列,只有在延迟期满时才能从中提取到元素,从而实现定时执行的线程池。而周期性执行是配合上层封装的其他类来实现的,可以看ScheduledExecutorService类的scheduleAtFixedRate方法。
newWorkStealingPool(int parallelism)简单翻译是任务窃取线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
使用ForkJoinPool的好处是,把1个任务拆分成多个“小任务”,把这些“小任务”分发到多个线程上执行。这些“小任务”都执行完成后,再将结果合并。
当线程发现自己的队列没有任务了,就会到别的线程的队列里获取任务执行。可以简单理解为”窃取“。
一般是自己的本地队列采取LIFO(后进先出),窃取时采用FIFO(先进先出),一个从头开始执行,一个从尾部开始执行,由于偷取的动作十分快速,会大量降低这种冲突,也是一种优化方式。
①无参创建方式:public static ExecutorService newWorkStealingPool() {return new ForkJoinPool//Runtime.getRuntime().availableProcessors()是获取当前系统可以的CPU核心数。(Runtime.getRuntime().availableProcessors(),ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true); }
②有参创建方式
public static ExecutorService newWorkStealingPool(int parallelism) {return new ForkJoinPool(parallelism,ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true); }
ThreadPoolExecutor类创建线程
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 省略... }
① 共7个参数如下:
(1)corePoolSize:核心线程数,线程池中始终存活的线程数。
(2)maximumPoolSize: 最大线程数,线程池中允许的最大线程数。
(3)keepAliveTime: 存活时间,线程没有任务执行时最多保持多久时间会终止。
(4)unit: 单位,参数keepAliveTime的时间单位,7种可选。
(5)workQueue: 一个阻塞队列,用来存储等待执行的任务,均为线程安全,7种可选。
较常用的是LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
(6)threadFactory: 线程工厂,主要用来创建线程,默及正常优先级、非守护线程。
(7)handler:拒绝策略,拒绝处理任务时的策略,4种可选,默认为AbortPolicy。
线程池的执行规则:
(1)当线程数小于核心线程数时,创建线程。
(2)当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
(3)当线程数大于等于核心线程数,且任务队列已满:
若线程数小于最大线程数,创建线程。
若线程数等于最大线程数,抛出异常,拒绝任务。
阿里代码规范《阿里巴巴Java开发手册》中明确不建议使用Executors类提供的这4种方法:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。Executors返回的线程池对象的弊端如下:FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。所以我们应该使用ThreadPoolExecutor类来创建线程池,根据自己需要的场景来创建一个合适的线程池。
8.线程安全问题
(1)Runnable的线程安全问题
/*** 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式* 1.卖票过程中出现重票、错票 ---》出现了线程的安全问题* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票* 3.如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。* 4.在java中,我们通过同步机制,来解决线程的安全问题。* 5.同步的方式,解决了线程的安全问题。---好处* 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。---局限性** 方式一:同步代码块* synchronized(同步监视器){* //需要被同步的代码* }** 说明:1.操作共享数据的代码,即为需要被同步的代码 --->不能包含代码多了,也不能包含代码少了。* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据* 3.同步监视器,俗称:锁。任何一个类的对象,都可以来充当锁。* 要求:多个线程必须要共用同一把锁。** 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。** 方式二:同步方法* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的**/class Windows1 implements Runnable{private int ticket = 100;@Overridepublic void run() {while(true){synchronized (this) {//此时的this:唯一的windows1的对象 if (ticket > 0) {try{Thread.sleep(100);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);ticket--;} else {break;}}}}
}public class WindowsTest1 {public static void main(String[] args) {Windows1 w = new Windows1();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
/*** 2.使用同步方法解决实现Runnable接口的线程安全问题** 关于同步方法的总结:* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。* 2. 非静态的同步方法,同步监视器是:this* 静态的同步方法,同步监视器是:当前类本身*/class Windows3 implements Runnable {private int ticket = 100;@Overridepublic void run() {while (true) {show();}}public synchronized void show() { //方法上加synchronized
// synchronized (this){if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);ticket--;}
// }}
}public class WindowsTest3 {public static void main(String[] args) {Windows3 w3 = new Windows3();Thread t1 = new Thread(w3);Thread t2 = new Thread(w3);Thread t3 = new Thread(w3);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
(2)Thread类的线程安全问题
/*** 使用同步代码块解决继承Thread类的方式的线程安全问题** 例子:创建三个c窗口卖票,总票数为100张*/
class Windows extends Thread{private static int ticket = 100;private static Object obj = new Object();@Overridepublic void run() {while(true){//正确的
// synchronized (obj) {synchronized (Windows.class){ //Class clazz = Windows.class//错误的,因为此时this表示的是t1,t2,t3三个对象
// synchronized (this) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + ":卖票,票号为: " + ticket);ticket--;} else {break;}}}}
}public class WindowsTest2 {public static void main(String[] args) {Windows t1 = new Windows();Windows t2 = new Windows();Windows t3 = new Windows();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
/*** 使用同步方法处理继承Thread类的方式中的线程安全问题*/
class Windows4 extends Thread {private static int ticket = 100;@Overridepublic void run() {while (true) {show();}}private static synchronized void show(){//同步监视器:Window4.class//private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);ticket--;}}
}public class WindowsTest4 {public static void main(String[] args) {Windows4 t1 = new Windows4();Windows4 t2 = new Windows4();Windows4 t3 = new Windows4();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
9.同步 异步 与 阻塞 非阻塞
(1)同步和异步
1.同步:是指在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
2.异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)。
(2)阻塞和非阻塞
1.阻塞调用:是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
2.非阻塞调用:指在不能立刻得到结果之前,该调用不会阻塞当前线程。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
举个栗子:
老张爱喝茶,废话不说,煮开水。
1.老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2.老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3.老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4.老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。
10.线程的 run()和 start()有什么区别?
- 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
- start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码; 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止,然后CPU再调度其它线程。
- run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
11.线程有哪几种状态
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的成为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得cpu 时间片后变为运行中状态(running)。
- 阻塞(BLOCKED):表线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
12.notify()和 notifyAll()有什么区别?
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的notifyAll()方法(唤醒所有 wait 线程)或notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
13.在 java 程序中怎么保证多线程的运行安全?
- 线程安全在三个方面体现:
- 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
- 可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile,final);
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
Happens-Before 规则如下:
- 程序次序规则:在一个线程内,按照程序控制流顺序,书写在前面的操作先行发生于书写在后面的操作
- 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
- volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作
- 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
14. runnable 和 callable 有什么区别?
- Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。 - Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
- 注意:Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
15.悲观锁和乐观锁
- 悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态。
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。 - 乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态。
乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
常见的锁:
- synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。 - ReentrantLock 排他锁(悲观锁,有罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。 - ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。
16.线程池中 submit()和 execute()方法有什么区别?
- execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
- execute() 没有返回值;而 submit() 有返回值
- submit() 的返回值 Future 调用get方法时,可以捕获处理异常
17.多线程锁的升级原理是什么?
1、多线程中锁的升级原理是什么?
锁的级别从低到高:
无锁——偏向锁——轻量级锁——重量级锁,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
没有优化以前,sychronized是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。
所以为了减少获得和释放锁带来的性能消耗,JVM 对 sychronized 关键字进行了优化,把锁分为 无锁、偏向锁、轻量级锁、重量级锁状态。
(1)无锁
没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。
(2)偏向锁
偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。
偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
(我觉得偏向锁和线程是对应关系。)
(3)轻量级锁
轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋(做一点其他的事情休息一下)的形式尝试获取锁,线程不会阻塞,从而提高性能。
轻量级锁的获取主要由两种情况:① 当关闭偏向锁功能时;② 由于多个线程竞争偏向锁导致偏向锁升级为轻量级锁。
(4)重量级锁
重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资源,导致性能低下。
2、什么是自旋锁?
自旋锁(spinlock):
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
(1)自旋锁是非公平的,无法满足等待时间最长的线程优先获取锁。
(2)自旋锁是非阻塞的,不会使线程状态发生切换,线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
18.String、StringBuffer和StringBuilder的区别
19.三次握手和四次挥手
20.聚合函数
21.redis数据类型
22.redis数据持久化(RDB和AOF)
23.hashmap的put方法的原理
24.String类的常用方法
二、每日练习(一天三题)
1.JVM中内存结构模型。
JVM内存结构和Java内存模型别再傻傻分不清了
一篇文章掌握整个JVM,JVM超详细解析!!!
常见JVM面试题及答案整理
2.Java中多线程在项目中哪里用到了?
3.活锁死锁问题分析,什么是活锁,什么是死锁?出现的原因是什么?如何避免?
1.什么是死锁
(1)死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
(2)死锁发生的条件:
- 互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
- 请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。
- 环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即:{p0,p1,p2,…pn},进程p0(或线程)等待p1占用的资源,p1等待p2占用的资源,pn等待p0占用的资源。(最直观的理解是,p0等待p1占用的资源,而p1而在等待p0占用的资源,于是两个进程就相互等待)
(3)如何避免死锁
- 设置优先级
setPriority() - 超时
放弃ReentrantLock接口中: boolean tryLock(long time, TimeUnit unit)
设置超时时间,超时可以退出防止死锁。 - B->C" data-link-title="特定顺序 A->B->C">特定顺序 A->B->C
- 尽量降低锁的使用力度,尽量不要几个功能用同一把锁,能锁块不锁方法
- 不要用锁
2.什么是活锁
- 活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。
4.多线程下如何做到数据共享?(可拓展数据共享的使用场景)
5.多线程下如何保证线程安全?(可拓展两种锁)
6.HashMap的底层详细介绍下?(1.7和1.8前后区别)
HashMap在JDK7中的底层实现原理
- HashMap的内部存储结构其实是数组和链表的结合。当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素。
- 每个bucket中存储一个元素,即一个Entry对象,但每一个Entry对象可以带一个引用变量,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Entry链。而且新添加的元素作为链表的head。
- 添加元素的过程:
- 向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i。
- 如果位置i上没有元素,则entry1直接添加成功。
- 如果位置i上已经存在entry2(或还有链表存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key的hash值和其他的entry的hash值。
- 如果彼此hash值不同,则直接添加成功。
- 如果hash值相同,继续比较二者是否equals。如果返回值为true,则使用entry1的value去替换equals为true的entry的value。
- 如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。
HashMap在JDK8中的底层实现原理
HashMap的内部存储结构其实是数组+链表+红黑树的结合。当实例化一个HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素
每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。
- 那么HashMap什么时候进行扩容和树形化呢?
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成红黑树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把红黑树再转为链表。
- 关于映射关系的key是否可以修改?answer:不要修改
映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。
jdk8 相较于jdk7在底层实现方面的不同:
- new HashMap():底层没有创建一个长度为16的数组
- jdk 8底层的数组是:Node[],而非Entry[]
- 首次调用put()方法时,底层创建长度为16的数组
- jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
- 形成链表时,七上八下(jdk7:新的元素指向旧的元素。jdk8:旧的元素指向新的元素)
- 当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。
尚硅谷Java零基础入门教程(含百道Java真题,2万多行Java代码实战)_哔哩哔哩_bilibili
7.ArrayList和LinkdList的区别?(使用场景和特性分析)
8.ConCurrentHashMap和HashMap的区别,分段锁机制?(理论)
9.HashMap和Hashtable的区别?
- Hashtable是java一开始发布时就提供的键值映射的数据结构,而HashMap产生于JDK1.2。
- HashMap继承自AbstractMap类,而Hashtable继承自Dictionary类(这是一个已经被废弃的类);
/*** <strong>NOTE: This class is obsolete. New implementations should* implement the Map interface, rather than extending this class.</strong>*/
Hashtable是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步;
HashMap不是线程安全的,在多线程并发的环境下,可能会产生死锁等问题。使用HashMap时就必须要自己增加同步处理,虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断,用containsValue()方法来判断是否存在某个值。
Hashtable既不支持Null key也不支持Null value:当key为Null时,调用put() 方法,运行到下面这一步就会抛出空指针异常,因为拿一个Null值去调用方法了;当value为null值时,Hashtable对其做了限制,运行到下面这步也会抛出空指针异常。Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
Hashtable比HashMap多提供了elments() 和contains() 两个方法;
elments() 方法继承自Hashtable的父类Dictionnary。elements() 方法用于返回此Hashtable中的value的枚举。
HashMap是没有contains方法的,而包括containsValue和containsKey方法;hashtable则保留了contains方法,效果同containsValue,还包括containsValue和containsKey方法。计算hash值方式不同:
为了得到元素的位置,首先需要根据元素的 KEY计算出一个hash值,然后再用这个hash值来计算得到最终的位置。①:HashMap有个hash方法重新计算了key的hash值,因为hash冲突变高,所以通过一种方法重算hash值的方法:先调用hashCode方法计算出来一个hash值,再将hash与右移16位后相异或,从而得到新的hash值。
②:Hashtable通过计算key的hashCode()**来得到hash值就为最终hash值。
整理自HashMap 与HashTable的区别_wangxing233的博客-CSDN博客_hashmap和hashtable的区别
10.NIO和AIO、BIO三者区别?(理论)
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
11.介绍下Spring框架。(框架简介、框架核心、框架特性等)
框架简介
Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。
为什么说Spring是一个一站式的轻量级开源框架呢?EE开发可分成三层架构,针对JavaEE的三层结构,每一层Spring都提供了不同的解决技术。
框架核心
IOC(Inverse of Control):控制反转,也可以称为依赖倒置
AOP:即面向切面编程
框架特点:
①、方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
②、AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
③、声明式事务的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
④、方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
⑤、方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts,Hibernate、Hessian、Quartz)等的直接支持。
⑥、降低Java EE API的使用难度
Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
⑦、Java 源码是经典学习范例
Spring的源码设计精妙、结构清晰、匠心独运,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。Spring框架源码无疑是Java技术的最佳实践范例。如果想在短时间内迅速提高自己的Java技术水平和应用开发水平,学习和研究Spring源码将会使你收到意想不到的效果。
Spring的优点
1.低侵入式设计,代码污染极低
2.独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺
3.Spring的DI机制降低了业务对象替换的复杂性,提高了组件之间的解耦
4.Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用
5.Spring的ORM和DAO提供了与第三方持久层框架的良好整合,并简化了底层的数据库访问
6.Spring并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部,Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
7.降低JavaEE API的使用难度,Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低。
12.介绍下Spring框架IOC和AOP的实现原理。(理论)
IOC(Inverse of Control):控制反转,也可以称为依赖倒置。
所谓依赖,从程序的角度看,就是比如A要调用B的方法,那么A就依赖于B,反正A要用到B,则A依赖于B。所谓倒置,你必须理解如果不倒置,会怎么着,因为A必须要有B,才可以调用B,如果不倒置,意思就是A主动获取B的实例:B b = new B(),这就是最简单的获取B实例的方法(当然还有各种设计模式可以帮助你去获得B的实例,比如工厂、Locator等等),然后你就可以调用b对象了。所以,不倒置,意味着A要主动获取B,才能使用B;到了这里,就应该明白了倒置的意思了。倒置就是A要调用B的话,A并不需要主动获取B,而是由其它人自动将B送上门来。
形象的举例就是:
通常情况下,假如你有一天在家里口渴了,要喝水,那么你可以到你小区的小卖部去,告诉他们,你需要一瓶水,然后小卖部给你一瓶水!这本来没有太大问题,关键是如果小卖部很远,那么你必须知道:从你家如何到小卖部;小卖部里是否有你需要的水;你还要考虑是否开着车去;等等等等,也许有太多的问题要考虑了。也就是说,为了一瓶水,你还可能需要依赖于车等等这些交通工具或别的工具,问题是不是变得复杂了?那么如何解决这个问题呢?解决这个问题的方法很简单:小卖部提供送货上门服务,凡是小卖部的会员,你只要告知小卖部你需要什么,小卖部将主动把货物给你送上门来!这样一来,你只需要做两件事情,你就可以活得更加轻松自在:
第一:向小卖部注册为会员。
第二:告诉小卖部你需要什么。这和Spring的做法很类似!Spring就是小卖部,你就是A对象,水就是B对象
第一:在Spring中声明一个类:A
第二:告诉Spring,A需要B假设A是UserAction类,而B是UserService类
<bean id="userService" class="org.leadfar.service.UserService"/> <bean id="documentService" class="org.leadfar.service.DocumentService"/> <bean id="orgService" class="org.leadfar.service.OrgService"/><bean id="userAction" class="org.leadfar.web.UserAction"><property name="userService" ref="userService"/> </bean>
在Spring这个商店(工厂)中,有很多对象/服务:userService,documentService,orgService,也有很多会员:userAction等等,声明userAction需要userService即可,Spring将通过你给它提供的通道主动把userService送上门来,因此UserAction的代码示例类似如下所示:
package org.leadfar.web; public class UserAction{private UserService userService;public String login(){userService.valifyUser(xxx);}public void setUserService(UserService userService){this.userService = userService;} }
实现原理:Spring的IoC的底层实现原理是工厂设计模式+反射+XML配置文件。
AOP:即面向切面编程
(1). AOP面向方面编程基于IoC,是对OOP的有益补充;
(2). AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了 多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的 逻辑或责任封装起来,比如日志记录,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
(3). AOP代表的是一个横向的关 系,将“对象”比作一个空心的圆柱体,其中封装的是对象的属性和行为;则面向方面编程的方法,就是将这个圆柱体以切面形式剖开,选择性的提供业务逻辑。而 剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹,但完成了效果。
(4). 实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
(5). Spring实现AOP:JDK动态代理和CGLIB代理 JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理;其核心的两个类是InvocationHandler和Proxy。 CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,性能比JDK强;需要引入包asm.jar和cglib.jar。使用AspectJ注入式切面和@AspectJ注解驱动的切面实际上底层也是通过动态代理实现的。
面向切面编程的目标就是分离关注点。什么是关注点呢?就是你要做的事,就是关注点。假如你是个公子哥,没啥人生目标,天天就是衣来伸手,饭来张口,整天只知道玩一件事!那么,每天你一睁眼,就光想着吃完饭就去玩(你必须要做的事),但是在玩之前,你还需要穿衣服、穿鞋子、叠好被子、做饭等等等等事情,这些事情就是你的关注点,但是你只想吃饭然后玩,那么怎么办呢?这些事情通通交给别人去干。在你走到饭桌之前,有一个专门的仆人A帮你穿衣服,仆人B帮你穿鞋子,仆人C帮你叠好被子,仆人C帮你做饭,然后你就开始吃饭、去玩(这就是你一天的正事),你干完你的正事之后,回来,然后一系列仆人又开始帮你干这个干那个,然后一天就结束了!
AOP的好处就是你只需要干你的正事,其它事情别人帮你干。也许有一天,你想裸奔,不想穿衣服,那么你把仆人A解雇就是了!也许有一天,出门之前你还想带点钱,那么你再雇一个仆人D专门帮你干取钱的活!这就是AOP。每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。
从Spring的角度看,AOP最大的用途就在于提供了事务管理的能力。事务管理就是一个关注点,你的正事就是去访问数据库,而你不想管事务(太烦),所以,Spring在你访问数据库之前,自动帮你开启事务,当你访问数据库结束之后,自动帮你提交/回滚事务!
13.Spring框架中beanFactory和FactoryBean有什么区别?
14.SpringBoot框架的项目启动流程?
第一部分:SpringApplication初始化模块,配置一些基本的环境变量,资源,监听器,构造器;
第二部分:实现了应用具体的启动方案,包括流程的监听模块,加载配置环境模块以及创建上下文环境模块
第三部分:自动化配置模块,这个模块是实现SpringBoot的自动配置
启动:
每个SpringBoot程序都有一个主入口,也就是main方法,main里面调用SpringApplication.run()启动整个spring-boot程序,该方法所在类有一个@SpringBootApplication注解,它是一个组合注解;
1. @EnableAutoConfiguration:SpringBoot根据应用所声明的依赖来对Spring框架进行自动配置
2. @SpringBootConfiguration(内部为@Configuration): 标注这个类是一个配置类;
3. @ComponentScan:组件扫描,可自动发现和装配Bean,自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中
SpringBoot启动类
首先进入run方法
run方法中去创建了一个SpringApplication实例,
- 使用SpringFactoriesLoader在应用的META-INFO/spring.facories中查找并加载所有可用的ApplicationContextInitializer。
- 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener。
在该构造方法内,我们可以发现其调用了一个初始化的initialize方法
该方法中实现了如下几个关键步骤:
1.创建了应用的监听器SpringApplicationRunListeners并开始监听
2.加载SpringBoot配置环境(ConfigurableEnvironment),如果是通过web容器发布,会加载StandardEnvironment
3.配置环境(Environment)加入到监听器对象中(SpringApplicationRunListeners)
4.创建run方法的返回对象:ConfigurableApplicationContext(应用配置上下文)
5.回到run方法内,prepareContext方法将listeners、environment、applicationArguments、banner等重要组件与上下文对象关联
6.接下来的refreshContext(context)方法(初始化方法如下)将是实现spring-boot-starter-*(mybatis、redis等)自动化配置的关键,包括spring.factories的加载,bean的实例化等核心工作。
refresh方法
配置结束后,Springboot做了一些基本的收尾工作,返回了应用环境上下文。回顾整体流程,Springboot的启动,主要创建了配置环境(environment)、事件监听(listeners)、应用上下文(applicationContext),并基于以上条件,在容器中开始实例化我们需要的Bean,至此,通过SpringBoot启动的程序已经构造完成
15.SpringBoot如何实现自动装配。
16.阐述下security框架的授权以及认证流程(带入项目中角色管理模块的实现思路,来阐述此题)
17.Redis的常见数据类型有哪些?(带入具体类型具体使用场景)
字符串string:
字符串类型是Redis中最为基础的数据存储类型,是一个由字节组成的序列,他在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据货Json对象描述信息等,是标准的key-value,一般来存字符串,整数和浮点数。Value最多可以容纳的数据长度为512MB
应用场景:1.很常见的场景用于统计网站访问数量,当前在线人数等。incr命令(++操作)
2.基于此类型,可以实现博客的字数统计,将日志不断追加到指定key,实现一个分布式自增iid,实现一个博客的的点赞操作等
列表list:
Redis的列表允许用户从序列的两端推入或者弹出元素,列表由多个字符串值组成的有序可重复的序列,是链表结构,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。List中可以包含的最大元素数量是4294967295。
应用场景:1.最新消息排行榜。2.消息队列,以完成多程序之间的消息交换。可以用push操作将任务存在list中(生产者),然后线程在用pop操作将任务取出进行执行。(消费者)
散列hash:
Redis中的散列可以看成具有String key和String value的map容器,可以将多个key-value存储到一个key中。每一个Hash可以存储4294967295个键值对。
应用场景:例如存储、读取、修改用户属性(name,age,pwd等)
集合set:
Redis的集合是无序不可重复的,和列表一样,在执行插入和删除和判断是否存在某元素时,效率是很高的。集合最大的优势在于可以进行交集并集差集操作。Set可包含的最大元素数量是4294967295。
应用场景:1.利用交集求共同好友。
2.利用唯一性,可以统计访问网站的所有独立IP。
3.好友推荐的时候根据tag求交集,大于某个threshold(临界值的)就可以推荐。
有序集合sorted set:
和set很像,都是字符串的集合,都不允许重复的成员出现在一个set中。他们之间差别在于有序集合中每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。尽管有序集合中的成员必须是卫衣的,但是分数(score)却可以重复。
应用场景:可以用于一个大型在线游戏的积分排行榜,每当玩家的分数发生变化时,可以执行zadd更新玩家分数(score),此后在通过zrange获取几分top ten的用户信息。
最后,还有个对key的通用操作,所有的数据类型都可以使用的
18.Redis的缓存雪崩和击穿如何解决,穿透问题遇到过吗?(带入具体的项目场景)
缓存雪崩
(1)概念:由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不可用(宕机)或者大量缓存由于超时,相同在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达存储层,存储层压力过大导致系统雪崩。
(2)解决方案:
① 可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
② 采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
③ 缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)
缓存击穿
(1)概念:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
缓存穿透
(1)概念:缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。
穿透示意图:
缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。
造成缓存穿透的基本原因有两个。第一,自身业务代码或者数据出现问题(例如:set 和 get 的key不一致),第二,一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)
(2)解决方案:
1. 缓存空对象
缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
缓存空对象会有两个问题:第一,value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
2. 布隆过滤器拦截
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
19.数据库中有2000w条数据,而Redis当中只有20w条数据,如何保证Redis中的数据一定是热点数据?(可考虑设置缓存过期时间)
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
redis 提供 6种数据淘汰策略:
- voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据
20.Spring框架中出现的ABA问题(循环依赖)如何解决?(原理,缓存思路)
- spring只能通过提前暴露bean来解决setter注入的循环依赖,无法解决构造器注入的循环依赖;不过话说回来,如果出现循环依赖,一般都是设计上的问题,但凡正经点的项目都不会出现这种问题,如果出现了,不用方,我们能做的就是把构造器创建bean改成setter,剩下的交给spring;
21.Spring框架中Bean对象的生命周期。(原理)
实例化
实例化一个Bean,也就是我们常说的new。
IOC依赖注入按照Spring 上下文对实例化的Bean 进行配置,也就是IOC 注入。
setBeanName实现如果这个Bean 已经实现了BeanNameAware 接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring 配置文件中Bean 的id 值
BeanFactoryAware实现如果这个Bean 已经实现了BeanFactoryAware 接口,会调用它实现的setBeanFactory,setBeanFactory(BeanFactory)传递的是Spring 工厂自身(可以用这个方式来获取其它Bean,只需在Spring 配置文件中配置一个普通的Bean 就可以)。
ApplicationContextAware实现如果这个Bean 已经实现了ApplicationContextAware 接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring 上下文(同样这个方式也可以实现步骤4 的内容,但比4 更好,因为ApplicationContext 是BeanFactory 的子接口,有更多的实现方法)
postProcessBeforeInitialization接口如果这个Bean 关联了BeanPostProcessor 接口,将会调用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用作是Bean 内容的更改,并且由于这个是在Bean 初始化结束时调用那个的方法,也可以被应用于内存或缓存技术。
init-method如果Bean 在Spring 配置文件中配置了init-method 属性会自动调用其配置的初始化方法。
postProcessAfterInitialization如果这个Bean关联了BeanPostProcessor 接口,将会调用postProcessAfterInitialization(Object obj, String s)方法。
注:以上工作完成以后就可以应用这个Bean 了,那这个Bean 是一个Singleton 的,所以一般情况下我们调用同一个id 的Bean 会是在内容地址相同的实例,当然在Spring 配置文件中也可以配置非Singleton。
Destroy 过期自动清理阶段当Bean 不再需要时,会经过清理阶段,如果Bean 实现了DisposableBean 这个接口,会调用那个其实现的destroy()方法;destroy-method 自配资清理
最后,如果这个Bean 的Spring 配置中配置了destroy-method 属性,会自动调用其配置的销毁方法。
22.SpringMVC框架的工作原理(带入项目中功能请求来阐述,例如登录请求、查询请求)
23.SpringMVC框架从发起请求到接收请求发生了什么?(网络相关)
24.MyBatis框架如何处理sql注入问题?(MyBatis中#和$符号区别)
可以使用 # 来防止SQL注入。例如:SELECT * FROM user where id = #{id}
在mybatis的查询中,还有一种写法是用$。$多用于动态查询$和#的区别:
① ${}:sql拼接符 #{}:参数占位符② # 将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id";
$ 将传入的数据直接显示生成在sql中。如:orderby将传入的数据直接显示生成在sql中。如:orderbyuser_id$,如果传入的值是111,那么解析成sql时的值为order by user_id,如果传入的值是id,则解析成的sql为order by id。
③ #可以防止SQL注入,$并不能防止SQL注入
Mybatis实现SQL注入的原理是调用了jdbc中的preparedStatement来进行预处理④ $方式一般用于传入数据库对象,例如传入表名。
⑤ 一般能用#的就别用$。
25.MyBatis框架有几级缓存,默认开启几级?(理论性)
Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。
MyBatis (五)一级缓存和二级缓存的区别_新新许愿树-CSDN博客_mybatis的一级缓存和二级缓存的区别
26.MySql数据库中索引的作用和优缺点?(理论内容)
作用:
索引相当于图书的目录,可以根据目录中的页码快速找到所需的内容。快速读取数据
优点:
(1)对于记录数量很多的表,可以提高查询速度。
(2)通过创建唯一性索引,可以保证表中数据的唯一性。
(3)可以加速表和表的连接,特别是在实现数据的参考完整性方面特别有意义。
(4)在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
(5)通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。缺点:
(1)索引是占用空间的。
(2)索引会影响增、删、改的速度。
(3)索引的创建和维护要耗费时间,索引数量越多,消耗时间越多
27.MySql中索引有几种,索引怎么使用?(带入具体的项目场景)
索引分类
(1) 普通索引:即不应用任何限制条件的索引,该索引可以在任何数据类型中创建;
(2) 唯一索引:使用UNIQUE参数可以设置唯一索引。创建该索引时,索引的值必须唯一,允许有空值;
(3) 主键索引:它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引;
(4) 全文索引:使用FULLTEXT参数可以设置全文索引。全文索引只能创建在CHAR、VARCHAR、TEXT类型的字段上。查询数据量较大的字符串类型字段时,使用全文索引可以提高查询速度。注意:全文索引在默认情况下是对大小写字母不敏感的,可以通过使用二进制对索引的列进行排序以执行大小写敏感的全文索引;
(5) 单列索引:顾名思义,单列索引值对应一个字段的索引。可以包括上述的三种索引方式。应用该索引的条件只需要保证该索引值对应一个字段即可;
(6) 多列索引:多列索引是在表的多个字段上创建一个索引。该索引只想创建时对应的多个字段,可以通过这几个字段进行查询。要想应用该索引,用户必须使用这些字段中的第一个字段;
(7) 空间索引:使用SPATIAL参数可以设置控件索引。控件索引只能建立在控件数据类型(LINESTRING、POINT、GEOMETRY等)上,这样可以提高系统获取控件数据的效率。MySQL中只有MyISAM存储引擎支持空间索引,且该字段不能为空值。索引的使用
- 需要创建索引的情况
主键自动创建索引
频繁作为查询条件的字段应该创建索引
查询中与其他表关联的字段,外键关系建立索引
查询中排序字段,排序字段需要建立索引(大大提高排序的速度)
频繁更新字段不适合创建索引
where条件里用不到的字段不创建索引
创建单键/组合索引的选择问题。高并发下倾向于创建组合索引
统计或分组字段需要创建索引 - 不需要创建索引的情况
表记录太少不需要创建索引
经常dml(增、删、改)操作的表不需要创建创建索引
数据重复且分布平均的表字段没有必要创建索引 - 索引选择性
表字段记录数/表该字段不重复的记录数 越接近于1索引效果越好
28.什么是事务?事务的特性?MySql中事务的隔离级别?(理论内容)
什么是事务?
将一组业务操作中的多条sql语句当成一个整体,那么这多条sql语句要么全部执行成功,要么全部执行失败。如果有一条sql语句执行失败,则回滚已经执行成功的sql语句。
事务的特性
原子性(atomicity):表示事务是一个不可再分割的工作单元,事务中的操作要么全部执行成功,要么全部执行失败。列如:张三转账给李四,出现异常,张三扣款失败,李四收款失败。
一致性(consistency):表示事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。列如张三转账给李四 1000元(张三现在有 3000元,李四现在有 2000元),不管这次的转账成功或者失败,两个人钱的总和都应该是 5000元。
隔离性(isolation):表示多个事务并发访问时,事务之间是隔离的互不干扰的,一个事务不应该影响其它事务运行效果。
持久性(durability):一个事务一旦提交成功,它对数据库中的数据的操作是永久的。
MySql中事务的隔离等级?
第一等级:(读未提交):会产生脏读,不可重复读,幻读的可能。
第二等级:(读已提交):不会产生脏读,但是会发生不可重复读,幻读的可能。(MySQL默认隔离级别)
第三等级:(可重复读):不会发生脏读和不可重复读,但是会发生幻读的可能。(Oracle默认隔离级别)
第四等级:(串行化):不会发生以上的三种问题。
第一等级安全系数最低,第四等级安全系数最高。
什么是脏读?什么是不可重复读?什么是幻读?什么是可重复读?
脏读:当前事务读取到了其它事务未提交的数据。
不可重复读:在一个事务中,查询结果是这个,当其它事务提交的时候,再查询发现结果变了,这种现象就是不可重复读。
幻读:事务A第一次查询得到一行记录a,事务B提交修改后记录b,事务A第二次查询得到两行记录,记录a和记录b。
可重复读:在一个事务中,别的事务提交了,查询结果和别的事务没提交前是一样的,查询结果不会变,这种现象叫做可以重复读。当本次事务提交了,再查询结果就会改变。
29.内存溢出是怎么回事?内存溢出和内存泄露的区别?
内存泄露
是指程序在申请内存后,无法释放已申请的内存空间就造成了内存泄漏,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
我们知道了内存泄漏的原因而内存溢出则有可能是因为我们我们多次内存泄漏堆积后的后果则变成了内存溢出。内存溢出
指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM(OutOfMemory),即所谓的内存溢出,简单来说就是自己所需要使用的空间比我们拥有的内存大内存不够使用所造成的内存溢出。
区别
30.项目中哪里使用多线程,哪里使用锁机制了?
三、应用
1.设计模式
常用的设计模式汇总,超详细!
2.单点登录
2021 Java面试题总结(更新中)相关推荐
- java取出字符串中的后四位_[原]Java面试题-将字符串中数字提取出来排序后输出...
[Title][原]Java面试题-将字符串中数字提取出来排序后输出 [Date]2013-09-15 [Abstract]很简单的面试题,要求现场在纸上写出来. [Keywords]面试.Java. ...
- java史上最全面试题--持续更新中(一)
1.面向对象的特征有哪些方面? 抽象:将同类对象的共同特征提取出来构造类. 继承:基于基类创建新类. 封装:将数据隐藏起来,对数据的访问只能通过特定接口. 多态性:不同子类型对象对相同消息作出不同响应 ...
- 美团点评、小米、菜鸟等等目前遇到的面试题(更新中)
美团面试题: 1.为什么要用SOA?有什么好处? 面向服务编程,是一种思想,一种方法论,一种分布式的服务架构, SOA是⼀种软件架构,它⽤于构建由⼀组松耦合,⿊盒组件组成的商业应⽤.每个组件代表 一个 ...
- 2021 Java面试题大全(整理版)1000+面试题附答案详解,最全面详细,看完稳了!
进大厂是大部分程序员的梦想,而进大厂的门槛也是比较高的,所以这里整理了一份阿里.美团.滴滴.头条等大厂面试大全,其中概括的知识点有:Java.MyBatis.ZooKeeper.Dubbo.Elast ...
- [剑指offer][JAVA]面试题[51][数组中的逆序对][归并排序]
[问题描述]面试题51.数组中的逆序对 (困难) 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 示例 1:输入: [7, ...
- JAVA面试大全(持续更新中...)
本文旨在收集Java面试过程中出现的问题,力求全面,仅作学习交流,欢迎补充,持续更新中-,部分段落选取自网上,部分引用文章已标注,部分已记不清了,如侵权,联系本人 Java基础 1.面向对象的概述 面 ...
- 史上最全的spark面试题——持续更新中
1.spark中的RDD是什么,有哪些特性? 答:RDD(Resilient Distributed Dataset)叫做分布式数据集,是spark中最基本的数据抽象,它代表一个不可变,可分区,里面的 ...
- 一道java面试题---去除list中的指定元素
一道比较简单的java面试题,一个list中存放string,例如存放人名,而且这个list的size比较大,现在要求去除该list中的名字为姓"张"的所有元素,至今没有想到很好的 ...
- Java面试-2021Gaoven-持续更新中
Java面试 一.基础 1.&&和&的区别? |和||的区别? 1.当符号左边是false时,&继续执行符号右边的运算.&&不再执行符号右边的运算. 2 ...
最新文章
- Python | 安装Jupyter Notebook及其目录的更改 jupyter问题
- thinkphp 使用外部php或html 原理
- form表单提交数据编码方式和tomcat接受数据解码方式
- 联想B450系列安装XP且开启AHCI
- 干货!全面认识Docker和基本指令
- 牺牲阳极计算机安装标准储罐,钢质储罐阴极保护牺牲阳极保护方法与设计安装...
- pads中如何设置等长_如何在Windows 10中设置和使用Hyper-V进行OS虚拟化
- 【C/C++】C++函数
- 一张有转折意义的神秘地图
- 5、ORB-SLAM闭环检测之通过求解出来的sim3寻找当前关键帧和闭环候选帧之间的更多匹配
- 回扣应该怎么给——某人的经验
- Python 科研风格字体(中文宋体、英文新罗马)
- 听我给你普及师父、师傅和讲师、教师的区别
- Java基础 EL表达式
- Permission is only granted to system apps解决方法
- 预编译器错误:代码使用了less语言,但未安装相应的编译器插件,请前往插件市场安装该插件
- 控制台调出Servers
- 云聚创新力量,助力多云互联:Tungsten Fabric在联通沃云峰会2019上分享开源SDN
- 关于curses 安装
- 从老板的裤裆拉链看 Google 管理之道
热门文章
- 用Python制作简单的俄罗斯方块游戏
- 恒大造车背后的资金战、技术战与时间战
- js JavaScript 计算时间差(两个日期时间相差的时间,包括毫秒,分钟,天数,差几个月,差几个季度)
- 一对老耗子,每个月都生一对小耗子。小耗子长3个月,第四个开始变成老耗子开始生! 假如都不死,那么请问24个月后有多少只耗子?...
- infortrend GSe Pro 208 NAS开箱初体验
- 【UNIAPP】APP快速免费生成一键发布页面
- 错误与异常 之 try...catch语句
- 如何用本地电脑部署一个网站
- Gala Games 推出最新的 3 款新游戏。
- 情人节送什么给男朋友比较好?情人节礼物分享