java面试题库精选

目录

1.线程和协程的区别

协程的应用场景

2.索引,以及它们的好处和坏处

3.请你说说多线程

4.说说怎么保证线程安全

5.请你说说死锁定义及发生的条件

6.请你说说进程间的通信方式

7.说说你对MVC的理解

8.详细的说说Redis的数据类型

String(字符串)

Hash(哈希)

List(列表)

Set(集合)

sadd 命令

zset(sorted set:有序集合)

zadd 命令

9.请你说说乐观锁和悲观锁

10.设计模式了解么

11.说说你对AOP的理解

12.说说Redis的持久化策略

13.请你讲讲单例模式、请你手写一下单例模式

概念

简单-懒汉式

14.请你说说虚拟内存和物理内存的区别

15.说说你对IoC的理解

16.请你说说内存管理

17.请你说说IO多路复用(select、poll、epoll)

18.请你说说线程和协程的区别

19.请你说说MySQL的事务隔离级别

20.如何利用Redis实现一个分布式锁?

21.请说说你对反射的了解

22.请你说说ArrayList和LinkedList的区别

23.请你说说聚簇索引和非聚簇索引

24.数据库为什么不用红黑树而用B+树?

25.请你说说Redis的数据类型

26.请你讲讲工厂模式,手写实现工厂模式

27.你知道哪些线程安全的集合?

28.请你说说ConcurrentHashMap

29.说说缓存穿透、击穿、雪崩的区别

30.Redis如何与数据库保持双写一致性

31.说说你了解的线程同步方式

32.请你说说innodb和myisam的区别?

33.String、StringBuffer、Stringbuilder有什么区别

34.请你说说HashMap底层原理

35.说说你了解的JVM内存模型

36.说说JVM的垃圾回收机制

37.说说类加载机制

38.epoll原理

I/O

事件

通知机制

epoll的通俗解释

epoll的API详解

1. int epoll_create(int size)

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

epoll的两种触发方式

1.水平触发的时机

2.边缘触发的时机

epoll与select、poll的对比

1. 用户态将文件描述符传入内核的方式

2. 内核态检测文件描述符读写状态的方式

3. 找到就绪的文件描述符并传递给用户态的方式

4. 重复监听的处理方式

epoll更高效的原因

39.请你说一下抽象类和接口的区别

40.请你说说==与equals()的区别

延伸:

==:

equals:

41.说说synchronize的用法及原理

42.说说你对AQS的理解

43.Java哪些地方使用了CAS

44.说说JVM的垃圾回收算法

45.请你说说Redis数据类型中的zset,它和set有什么区别?底层是怎么实现的?

46.说说static修饰符的用法

47.说说线程的状态

48.说说你对ThreadLocal的理解

49.说说Spring Boot常用的注解

50.说说Bean的生命周期

51.synchronized和Lock有什么区别

52.说说volatile的用法及原理

53.说说Redis的单线程架构

54.如何实现Redis高可用

55.请你说一下final关键字

56.请你说说重载和重写的区别,构造方法能不能重写

57.请说说你对Java集合的了解

58.请你说说IO多路复用

59.请你说说索引怎么实现的B+树,为什么选这个数据结构?

60.请你讲一下Java 8的新特性

61.请你说说泛型、泛型擦除

62.说说你了解的线程通信方式

63.请你说说JUC

64.请你说说HashMap和Hashtable的区别

65.HashMap是线程安全的吗?如果不是该如何解决?

66.请你说说Java的四种引用方式

67.请你讲下G1垃圾回收器

68.请你说说内存溢出

69.请你说说内存泄漏

70.请你说说数据库引擎有哪些,各自有什么区别

71.简单介绍Spring

72.介绍一下MyBatis的缓存机制

73.请你说说String类,以及new和

74.请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()

75.说说线程的创建方式

76.说说你对ArrayList的理解

77.请你说说BIO、NIO、O

78.说说你对Spring Boot的理解,以及它和Spring的区别?

79.说说Spring Boot的自动装配

80.说说@Autowired和@Resource注解的区别

81.说说Redis的主从同步机制

82.说说Redis的缓存淘汰策略

83.说说垃圾收集器

84.请你说说Java的特点和优点,为什么要选择Java?

85.介绍一下包装类的自动拆装箱与自动装箱

86.说说wt()和sleep()的区别

87.说说你对线程池的理解

88.简单说下你对JVM的了解

89.说说Java运行时数据区

90.请你讲下CMS垃圾回收器

91.说说JVM的双亲委派模型

92.请你说说数据库索引的底层数据结构

93.说说Spring Boot 的启动流程

94.介绍一下Spring MVC的执行流程

95.在MyBatis中$和#有什么区别

96.请你说说Java基本数据类型和引用类型

97.请你说说Java的异常处理机制

98.说说你对面向对象的理解

99.请介绍一下访问修饰符

100.说说Java中常用的锁及原理

101.请你说说List与Set的区别

102.请你讲一下Java NIO

103.说说GC的可达性分析

104.说说类的实例化过程

105.请你讲讲B树和B+树

106.MySQL主从同步是如何实现的?

107.请你介绍一下数据库的ACID

108.请你说说数据库的索引是什么结构,为什么不用哈希表

109.请你说说InnoDB的MVCC

110.说说Spring Boot的起步依赖

111.说说Spring事务管理

113.说说Bean的作用域,以及默认的作用域

114.说说BeanFactory和FactoryBean的区别

115.说说你对Redis的了解


整理出了一百多道面试题目,供自己日常浏览,也欢迎大家关注收藏!

1.线程和协程的区别

  1. 由于协程的特性, 适合执行大量的I/O 密集型任务, 而线程在这方面弱于协程
  2. 协程涉及到函数的切换, 多线程涉及到线程的切换, 所以都有执行上下文, 但是协程不是被操作系统内核所管理, 而完全是由程序所控制(也就是在用户态执行), 这样带来的好处就是性能得到了很大的提升, 不会像线程那样需要在内核态进行上下文切换来消耗资源,因此协程的开销远远小于线程的开销。
  3. 同一时间, 在多核处理器的环境下, 多个线程是可以并行的,但是运行的协程的函数却只能有一个其他的协程的函数都被suspend, 即协程是并发的。
  4. 由于协程在同一个线程中, 所以不需要用来守卫临界区段的同步性原语(primitive)比如互斥锁、信号量等,并且不需要来自操作系统的支持。
  5. 在协程之间的切换不需要涉及任何系统调用或任何阻塞调用。
  6. 通常的线程是抢先式(即由操作系统分配执行权), 而协程是由程序分配执行权。

延伸:

协程(coroutine)是一种程序运行的方式即在单线程里多个函数并发地执行。

 在协程中的函数, 如果执行过程中遇到了I/O密集型任务, 被给定的YieldInstruction(让出指令)暂停执行(yield), 交出执行权到其他函数, 直到被给定的YieldInstruction(让出指令)结束, 再继续执行。

协程的原理:

当出现IO阻塞的时候,由协程的调度器进行调度,通过将数据流立刻yield掉(主动让出),并且记录当前栈上的数据,阻塞完后立刻再通过线程恢复栈,并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为Fiber。比如Golang里的 go关键字其实就是负责开启一个Fiber,让func逻辑跑在上面。

由于协程的暂停完全由程序控制,发生在用户态上;而线程的阻塞状态是由操作系统内核来进行切换,发生在内核态上。
因此,协程的开销远远小于线程的开销,也就没有了ContextSwitch上的开销。

协程的应用场景

协程的应用场景主要在于 :I/O 密集型任务

这一点与多线程有些类似,但协程调用是在一个线程内进行的,是单线程,切换的开销小,因此效率上略高于多线程;

当程序在执行 I/O 时操作时,CPU 是空闲的,此时可以充分利用 CPU 的时间片来处理其他任务;

在单线程中,一个函数调用,一般是从函数的第一行代码开始执行,结束于 return 语句、异常或者函数执行(也可以认为是隐式地返回了 None );

有了协程,我们在函数的执行过程中,如果遇到了I/O密集型任务,函数可以临时让出控制权,让 CPU 执行其他函数,等 I/O 操作执行完毕以后再收回其他函数的控制权。

2.索引,以及它们的好处和坏处

好处:

1、帮助用户高查询

2、利用索引的唯一性来控制记录的唯一性

3、可以加速表与表之间的连接

4、降低查询中分组和排序的时间

坏处:

1、存储索引占用磁盘空间

2、执行修改操作(insert、delete、update)产生索引维护

延伸:

索引是为了加速对表中数据行的检索而创建的一种分散的存储结构实现原理(B+树具体可以看:MySQL索引底层:B+树详解 - 知乎 通俗点说类似于一本书的目录,通过目录可以快速查到你想要的数据

索引分类
   1.普通索引     

普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度。

语句:

创建索引语句:

CREATE INDEX index_name ON table(column)

修改表时加索引语句:

ALTER TABLE table_name ADD INDEX index_name ON (column)

2.唯一索引

索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。

创建唯一索引语句:

CREATE UNIQUE INDEX indexName ON table(column)

修改表时加索引语句:

ALTER TABLE table_name ADD UNIQUE indexName ON (column)

3.主键索引

是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。

创建主键索引语句:

CREATE TABLE 'table' (
    'id' int(11) NOT NULL AUTO_INCREMENT ,   
    PRIMARY KEY ('id')
);

4.复合索引(组合索引)

用户可以在多个列上建立索引,这种索引叫做复合索引(组合索引) 查询时使用创建时第一个开始 索引才有效 使用遵循左前缀集合

复合语句:

ALTER TABLE `table` ADD INDEX indexName (name,xb,age); 

3.请你说说多线程

线程是操作系统调度的最小单元,它可以让一个进程并发地处理多个任务,也叫轻量级进程。所以,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈、局部变量,并且能够共享进程内的资源。由于共享资源,处理器便可以在这些线程之间快速切换,从而让使用者感觉这些线程在同时执行。 总的来说,操作系统可以同时执行多个任务,每个任务就是一个进程。进程可以同时执行多个任务,每个任务就是一个线程。一个程序运行之后至少有一个进程,而一个进程可以包含多个线程,但至少要包含一个线程。

延伸:

提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。

所谓串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。

进程:电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑。

什么是线程呢?

进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

 1. 更多的CPU核心:现代计算机处理器性能的提升方式,已经从追求更高的主频向追求更多的核心发展,所以处理器的核心数量会越来越多,充分地利用处理器的核心则会显著地提高程序的性能。而程序使用多线程技术,就可以将计算逻辑分配到多个处理器核心上,显著减少程序的处理时间,从而随着更多处理器核心的加入而变得更有效率。

 2. 更快的响应时间:我们经常要针对复杂的业务编写出复杂的代码,如果使用多线程技术,就可以将数据一致性不强的操作派发给其他线程处理(也可以是消息队列),如上传图片、发送邮件、生成订单等。这样响应用户请求的线程就能够尽快地完成处理,大大地缩短了响应时间,从而提升了用户体验。

3. 更好的编程模型:Java为多线程编程提供了良好且一致的编程模型,使开发人员能够更加专注于问题的解决,开发者只需为此问题建立合适的业务模型,而无需绞尽脑汁地考虑如何实现多线程。一旦开发人员建立好了业务模型,稍作修改就可以将其方便地映射到Java提供的多线程编程模型上。

4.说说怎么保证线程安全

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

如何保证呢: 1、使用线程安全的类; 2、使用synchronized同步代码块,或者用Lock锁;

由于线程安全问题,使用synchronized同步代码块 原理:当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。 另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

导致线程不安全的原因,主要有三点:

  • 原子性:一个或者多个操作在CPU执行的过程中被中断
  • 可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到
  • 有序性:程序执行的顺序没有按照代码的先后顺序执行
  • 静态编译器是将.java文件编译成.class文件,之后便可以解释执行。
  • 动态编译器是将.class文件编译成机器码,之后再由jvm运行。
  • 有时候,动态编译器为了程序的整体性能会对指令进行重排序

    虽然重排序可以提升程序的性能,但是重排序之后会导致源代码中指定的内存访问顺序与实际的执行顺序不一样,就会出现线程不安全的问题。

  • java程序如何保证线程安全呢?

    针对原子性问题:

  • JDK里面提供了很多atomic类比如AtomicInteger、AtomicLong、AtomicBoolean等等这些类本身可以通过CAS来保证操作的原子性。另外Java也提供了各种锁机制,来保证锁内的代码块在同一时刻只能有一个线程执行比如使synchronized加锁这样,就能够保证一个线程在对资源进行读、改、写操作时,其他线程不可对此资源进行操作从而保证了线程的安全性。

    针对可见性问题:

  • 同样可以通过synchronized关键字加锁来解决。与此同时,java还提供了volatile关键字,要优于synchronized的性能,同样可以保证修改对其他线程的可见性。volatile一般用于对变量的写操作不依赖于当前值的场景中,比如状态标记量等。

    针对重排序问题:

  • 可以通过synchronized关键字定义同步代码块或者同步方法保障有序性另外也可以通过Lock接口来保障有序性。

5.请你说说死锁定义及发生的条件

死锁定义:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。(集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。)

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之

一不满足,就不会发生死锁。

死锁的处理方法
1、预防死锁:事先预防策略,容易实现,通过实现设置限制,破坏产生死锁的四个条件之一。(如对资源采用按序分配策略)
2、避免死锁:事先预防策略,在资源的动态分配过程中,用某些方法防止系统禁图不安全状态。常见的方法有银行家算法。
3、检测死锁:通过检测机构等及时检测出死锁,采取适当措施,把进程从死锁中解脱。
4、解除死锁:检测出死锁后,采取措施解决。比如剥夺资源,撤销进程。
这四种方法对死锁的防范逐渐减弱,但对应的是资源利用率的提高。

6.请你说说进程间的通信方式

1.管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间用。进程的亲缘关系通常是指父子进程关系。
2.管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
3.消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4.共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
5.量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6.套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
7.信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
 

7.说说你对MVC的理解

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,主要提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。主要使用的技术:数据模型:实体类(JavaBean),数据访问:JDBC,Hibernate等。

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,比如JSP,Html等

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。主要使用的技术:servlet,Struts中的Action类等。

    MVC是一个框架模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型、视图、控制器。它们各自处理自己的任务。最典型的MVC就是JSP + servlet + javabean的模式。

MVC 是软件工程里面一个常用的概念。通常来讲,C 主要是指请求 url 的路由规则,即把请求打到对应的控制器。控制器调用模型实现业务逻辑和底层数据的获取,这一步骤一般在代码里会抽象出多个层次,比如 service,model,handler,controller 等等,看开发者的个人喜好,统称为 Model 层。最后交给渲染器渲染成 html 这样的客户端直接可用的格式,返回给客户端,这一步骤一般叫视图层。不过 MVC 在 web 开发里面已经有点昨日黄花的意味。现在讲究服务化,REST等等,前后端进一步分离,出现专门写逻辑的前端和 MVVM 模式,View 层在后端可以说已经用不到了,当然你也可以把生成的 json 理解成 View 层。MVC 模式里面容易让人误解的就是 Model 层,其实 model 层不仅包含了从数据库获取数据,还包含了与请求相关的各种业务逻辑,可以说 model 层包含了你 90% 以上的代码。


在代码中,前后端分离项目,大多就是controller-service-dao三层架构,三层架构和mvc并不是两个对立的东西,service层与dao层是对model的进一步解耦而形成的分层,而controller(web)层本来就不该含有业务逻辑
 

(我以前以为model是数据,controller是逻辑,这是错的)

很多程序员偏爱于将业务逻辑放在Controller中,我极力反对这种做法。

我们知道在写程序时,业务逻辑的重复使用是经常要面对的场景。 如果业务逻辑写在控制器中, 要重用它的唯一方法就是将它提升到父类之中,通过继承来达到代码复用的效果。但这么做会带来一个巨大的副作用,违背了一项重要的面向对象设计原则:接口隔离。(?)

各Model之间是可以相互调用的, Controller也可以无障碍的调用Model,因此将业务逻辑放在Model中可以灵活的使用组合的方式复用代码。

而Controller之间是不可以相互调用的,要复用代码只能将代码提升至父类,通过继承实现,显然这种做法既不正确,也不灵活,因此完全不提倡。

8.详细的说说Redis的数据类型

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

String(字符串)

string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。

string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

Hash(哈希)

Redis hash 是一个键值(key=>value)对集合。

Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。

List(列表)

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

Set(集合)

Redis 的 Set 是 string 类型的无序集合。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

sadd 命令

添加一个 string 元素到 key 对应的 set 集合中,成功返回 1,如果元素已经在集合中返回 0

zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

zadd 命令

添加元素到集合,元素在集合中存在则更新对应score

java面试题库精选_第1张图片

9.请你说说乐观锁和悲观锁

悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的;

特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;

手动加悲观锁:读锁LOCK tables test_db read释放锁UNLOCK TABLES;

写锁:LOCK tables test_db WRITE释放锁UNLOCK TABLES;

乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);

特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。

10.设计模式了解么

  • 什么是设计模式?

    • 某个问题的固定的解决方案。(可以被重复使用。)

  • 你知道哪些设计模式?

    • GoF设计模式:

      • 通常我们所说的23种设计模式。(Gang of Four:4人组提出的设计模式)

      • 单例模式

      • 工厂模式

      • 代理模式

      • 门面模式

      • 责任链设计模式

      • 观察者模式

      • 模板方法设计模式

      • .....

    • JavaEE设计模式:

      • DAO

      • DTO

      • VO

      • PO

      • pojo

      • ....

    • ....

  • 什么是模板方法设计模式?

  • 在模板类的模板方法当中定义核心算法骨架,具体的实现步骤可以延迟到子类当中完成。

  • 模板类通常是一个抽象类,模板类当中的模板方法定义核心算法,这个方法通常是final的(但也可以不是final的)
  • 模板类当中的抽象方法就是不确定实现的方法,这个不确定怎么实现的事儿交给子类去做。

快速记忆23种设计模式 - 知乎

【设计模式目录】简单阐述一下设计模式 - 掘金

11.说说你对AOP的理解

AOP全称叫做 Aspect Oriented Programming 面向切面编程。它是为解耦而生的,解耦是程序员编码开发过程中一直追求的境界,AOP在业务类的隔离上,绝对是做到了解耦,在这里面有几个核心的概念:

  • 切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以@Aspect注解(@AspectJ 注解方式)来实现。

  • 连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。

  • 通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。

  • 切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。

  • 引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现 IsModified接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。

  • 目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。

  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。

  • 织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。

    这些概念都太学术了,如果更简单的解释呢,其实非常简单:

    任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。

12.说说Redis的持久化策略

redis的持久化方式有俩种,持久化策略有4种:

RDB(数据快照模式),定期存储,保存的是数据本身,存储文件是紧凑的AOF(追加模式),每次修改数据时,同步到硬盘(写操作日志),保存的是数据的变更记录如果只希望数据保存在内存中的话,俩种策略都可以关闭也可以同时开启俩种策略,当Redis重启时,AOF文件会用于重建原始数据RDBRDB定时备份内存中的数据集。服务器启动的时候,可以从 RDB 文件中恢复数据集。

优点

存储的文件是紧凑的
适合用于备份,方便恢复不同版本的数据
适合于容灾恢复,备份文件可以在其他服务器恢复
最大化了Redis的性能,备份的时候启动的是子线程,父进程不需要执行IO操作
数据保存比AOF要快

缺点

如果Redis因为没有正确关闭而停止工作是,到上个保存点之间的数据将会丢失
由于需要经常fork子线程来进行备份操作,如果数据量很大的话,fork比较耗时,如果cpu性能不够,服务器可能是卡顿。属于数据量大的时候,一个服务器不要部署多个Redis服务。

创建快照有以下5种形式:

客户端发送BGSAVE指令,服务端会fork一条子线程将快照写入磁盘客户端发送SAVE指令,服务端在主线程进行写入动作。一般不常使用,一般在内存不够去执行BGSVAE的时候才用设置了SAVE配置项,如SAVE 300 100,那么当“300秒内有100次写入”时,Redus会自动触发BGSAVE命令。如果有多个配置项,任意一个满足,都会触发备份Redis通过SHUTDOWN命令接收到关闭服务器的请求、或者TERM信号时,会执行SAVE命令,这时候会阻塞所有客户端,不在执行客户端发送的任何命令当一个Redis服务器连接另外一个Redis服务器,并像对方发送SYNC命令开始一次复制操作时,如果主服务器目前没有在执行BGSAVE操作,或者主服务器刚刚执行完,那么主服务器就会执行GBSAVERedis的持久化策略 - 腾讯云开发者社区-腾讯云

13.请你讲讲单例模式、请你手写一下单例模式

1.概念
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
 

单例模式就是严格控制一个类的实例,要求在一个进程或一个线程中只存在一个实例,如果没有就创建一个实例返回,如果有就返回已经已经存在的实例。不同的单例模式都是为了确保只有一个实例。

2.特点
单例类在一个进程或线程中只能存在一个实例。
单例类的构造器必须私有化。不允许其它类来创建单例类,只有它自己能够创建自己。
单例类需要向其它类提供获取实例的方法,一般是静态的。
3.优点
减少了内存的开销。因为单例模式要做是严格控制只能存在一个实例,这样就会极大地减少实例反复创建时带来的内存开销。
更加容易管理和维护。没什么好说的,全部内容都写在这个类里,只需要修改这个类就行了。
4.缺点
扩展性不强。单例模式只专注于只有能存在一个实例,这就牺牲了它的扩展性。
与单一职责冲突。因为所有方法都要封装在单例类中,所以会越来越庞大,职责也会越来越不清晰。
二、单例模式的分类

1.饿汉式
就像饿汉遇到美食一样,一下子就全部吃完。
饿汉式单例,就是在装载.class文件是就会在静态区生成一个实例,其它类都是调用这个类。

package singletion;
/**
 * @author xxj
 * 饿汉单例
 */
public class HungrySingletion {
    private static HungrySingletion instance=new HungrySingletion();
    private HungrySingletion(){}
    public static HungrySingletion getInstance(){
        return instance;
    }
}

 特点

饿汉式就是简单粗暴,一上来就在静态区生成一个实例保证了线程安全,不会产生多余的实例,但是创建的实例会导致初始化比较缓慢。下一种单例模式就是解决了这个问题。

2.懒汉式

概念

懒汉嘛就是懒,不到火烧眉毛的时候绝对不会去做事情。
懒汉式单例,就是只在调用向其它类提供获取实例的方法时,才会去生成实例,但是这样就会存在一些线程问题。

简单-懒汉式

package singletion;
/**
 * @author xxj
 * 懒汉式单例
 */
public class LazySingleton {
    private static LazySingleton instance=null;
    private LazySingleton(){}
    public static LazySingleton  getInstance(){
    	if(instance==null)
        instance=new LazySingleton();
        return instance;
    }
}

 特点

一开始instance指向的是null,所以它不会一上来就在静态区生成一个实例,而是延迟加载,但是它存在线程问题。一旦多个线程同时访问这个获取实例的方法时,就会出现创建多个实例的情况,那它在这种极端的情况下,就不能算是单例模式了,所以下一个懒汉模式解决了这个问题。

3.静态内部

public class Singleton {  
    private static class SingletonHolder {  
    	private static final Singleton instance = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.instance ;  
    }  
}

解释
这种使用静态内部类实现的单例模式,其实是饿汉式的升级版,首先是在单例类的内部创建了一个静态内部类,内部类里面有一个单例类的实例;然后,单例类给其它类提供一个获取实例的方法。
当其它类调用这个方法时,类加载器才会去装载内部类,一装载,就会生成一个单例类的实例。

特点
不仅继承了饿汉式的线程安全的特性,而且实现了延迟加载的功能。
4.枚举式

package singletion;
/**
 * @author xxj
 * 枚举类单例模式
 */
public enum  EnumSingleton {
    INSTANCE;
    public static EnumSingleton getInstance() {
        return EnumSingleton.INSTANCE;
    }
}

 特点

首先即使实现简单方便,用枚举类实现单例实际上和饿汉式是一样的,它可以保证线程安全,但是同样初始化比较缓慢
再说一点,枚举类的确可以实现单例化,我验证过hashCode,都是一样的,但是它的写法就有点让人摸不着头脑。

【设计模式】面试官:请你手写一个单例模式!我:我要写五个! - 掘金

14.请你说说虚拟内存和物理内存的区别

操作系统有虚拟内存与物理内存的概念。在很久以前,还没有虚拟内存概念的时候,程序寻址用的都是物理地址。程序能寻址的范围是有限的,这取决于CPU的地址线条数。比如在32位平台下,寻址的范围是2^32也就是4G。并且这是固定的,如果没有虚拟内存,且每次开启一个进程都给4G的物理内存,就可能会出现很多问题:

因为我的物理内存时有限的,当有多个进程要执行的时候,都要给4G内存,很显然你内存小一点,这很快就分配完了,于是没有得到分配资源的进程就只能等待。当一个进程执行完了以后,再将等待的进程装入内存。这种频繁的装入内存的操作是很没效率的
由于指令都是直接访问物理内存的,那么我这个进程就可以修改其他进程的数据,甚至会修改内核地址空间的数据,这是我们不想看到的因为内存时随机分配的,所以程序运行的地址也是不正确的。于是针对上面会出现的各种问题,虚拟内存就出来了。

进程分配资源介绍过一个进程运行时都会得到4G的虚拟内存。这个虚拟内存你可以认为,每个进程都认为自己拥有4G的空间,这只是每个进程认为的,但是实际上,在虚拟内存对应的物理内存上,可能只对应的一点点的物理内存,实际用了多少内存,就会对应多少物理内存。

进程得到的这4G虚拟内存是一个连续的地址空间(这也只是进程认为),而实际上,它通常是被分隔成多个物理内存碎片,还有一部分存储在外部磁盘存储器上,在需要时进行数据交换。

进程开始要访问一个地址,它可能会经历下面的过程每次我要访问地址空间上的某一个地址,都需要把地址翻译为实际物理内存地址所有进程共享这整一块物理内存,每个进程只把自己目前需要的虚拟地址空间映射到物理内存上进程需要知道哪些地址空间上的数据在物理内存上,哪些不在(可能这部分存储在磁盘上),还有在物理内存上的哪里,这就需要通过页表来记录页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)当进程访问某个虚拟地址的时候,就会先去看页表,如果发现对应的数据不在物理内存上,就会发生缺页异常缺页异常的处理过程,操作系统立即阻塞该进程,并将硬盘里对应的页换入内存,然后使该进程就绪,如果内存已经满了,没有空地方了,那就找一个页覆盖,至于具体覆盖的哪个页,就需要看操作系统的页面置换算法是怎么设计的了。
 

15.说说你对IoC的理解

spring是一个ioc容器,容器就是放数据的,java里面的容器就是集合类。ioc容器实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点||repository、service、controller、component),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射new对象放到map里;扫描到打上上述注解的类还是通过反射new对象放到map里。

这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired、resource等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。

至于为什么要这么做,要知道工厂模式(控制反转)解决了什么问题又带来了什么问题。

1.解决的问题:解决了类与类之间强引用关系的问题,强引用关系导致了代码的耦合性高,维护性差。(控制反转的作用,举个例子,现在有个变量String name=“小明”,在两个类里都要用到小明这个字符串,如果不使用控制反转,在class A中要定义一个String name=“小明”;在class B中也要定义一个String name=“小明”;这个时候我需要把小明改成李四,那我需要找到所有用到“小明”的类然后一个一个去修改。实际上我们是定义一个静态常量,然后在需要用到的地方进行引用,这样的话当我们需要把小明修改成李四只需要去定义这个静态常量的那个类里去修改一次,所有引用到的地方都被修改了。这其实就是控制反转,定义静态常量的类就是工厂类,被定义的常量就是ioc容器内的各种对象)

2.带来的问题:导致工厂类的代码冗长,每增加一个接口都要在工厂类里加一段代码。

3.为了解决工厂模式带来的这个问题,spring通过配置化的方式来解决,也就是上面说的xml配置文件(通过遍历bean节点的方式new对象)

4.随着时间的发展,程序员们又觉得使用xml配置文件很麻烦,因为项目大了之后,各种配置文件眼花缭乱,就开始了注解之路了,于是这个时候SpringBoot出现了.....

16.请你说说内存管理

内存管理(Memory Management)是操作系统设计中最重要和最复杂的内容之一。虽然计算机硬件一直在飞速发展,内存容量也在不断增长,但是仍然不可能将所有用户进程和系统所需要的全部程序和数据放入主存中,所以操作系统必须将内存空间进行合理地划分和有效地动态分配。操作系统对内存的划分和动态分配,就是内存管理的概念。

有效的内存管理在多道程序设计中非常重要,不仅方便用户使用存储器、提高内存利用率,还可以通过虚拟技术从逻辑上扩充存储器。

内存管理的功能有:

  • 内存空间的分配与回收:由操作系统完成主存储器空间的分配和管理,使程序员摆脱存储分配的麻烦,提高编程效率。
  • 地址转换:在多道程序环境下,程序中的逻辑地址与内存中的物理地址不可能一致,因此存储管理必须提供地址变换功能,把逻辑地址转换成相应的物理地址。
  • 内存空间的扩充:利用虚拟存储技术或自动覆盖技术,从逻辑上扩充内存。
  • 存储保护:保证各道作业在各自的存储空间内运行,互不干扰。

17.请你说说IO多路复用(select、poll、epoll)

IO多路复用的三种机制Select,Poll,Epoll - 简书

IO多路复用的三种机制Select、Poll、Epoll_一口Linux的博客-CSDN博客

18.请你说说线程和协程的区别

线程和协程的区别的通俗说明 - 知乎

19.请你说说MySQL的事务隔离级别

MySQL事务隔离级别和实现原理(看这一篇文章就够了!) - 知乎

20.如何利用Redis实现一个分布式锁?

【Redis】利用 Redis 实现分布式锁 - 周二鸭 - 博客园

21.请说说你对反射的了解

谈谈你对反射的理解_摸鱼使我快乐的博客-CSDN博客_说说你对反射的理解

22.请你说说ArrayList和LinkedList的区别

什么是反射?你敢说你非常了解吗? - 知乎

23.请你说说聚簇索引和非聚簇索引

【java基础】程序员你真的理解反射机制吗?_宜春的博客-CSDN博客

24.数据库为什么不用红黑树而用B+树?

数据库索引为什么要用 B+ 树而不用红黑树呢?_buyulian的博客-CSDN博客_索引为什么不用红黑树

25.请你说说Redis的数据类型

数据库:为什么使用B+树而不使用红黑树_五山口老法师的博客-CSDN博客

26.请你讲讲工厂模式,手写实现工厂模式

手写工厂模式_喵了个咪的回忆丶的博客-CSDN博客_手写工厂模式

27.你知道哪些线程安全的集合?

Java线程安全的集合详解_「已注销」的博客-CSDN博客_线程安全的集合

28.请你说说ConcurrentHashMap

说说ConcurrentHashMap - 知乎

29.说说缓存穿透、击穿、雪崩的区别

缓存穿透、缓存击穿、缓存雪崩区别和解决方案_每天进步一点点yes的博客-CSDN博客_缓存击穿

30.Redis如何与数据库保持双写一致性

Redis 与 MySQL 双写一致性如何保证?_戴国进的博客-CSDN博客_redis如何保证双写一致

如何保证Redis和数据库双写一致性的问题? - 腾讯云开发者社区-腾讯云

31.说说你了解的线程同步方式

线程同步的几种方式 - sherlock_lin - 博客园

32.请你说说innodb和myisam的区别?

MyISAM与InnoDB 的区别(9个不同点)_Chackca的博客-CSDN博客_myisam

33.String、StringBuffer、Stringbuilder有什么区别

String、StringBuffer、StringBuilder的区别——(详细叙述)_开机发呆小菜鸟的博客-CSDN博客

34.请你说说HashMap底层原理

最通俗易懂搞定HashMap的底层原理 - 知乎

35.说说你了解的JVM内存模型

JVM内存模型(详解) - 知乎

36.说说JVM的垃圾回收机制

JVM垃圾回收机制,万字详解_黑马程序员官方的博客-CSDN博客_jvm垃圾回收机制

37.说说类加载机制

Java类加载机制 - 你个小秃头 - 博客园

38.epoll原理

epoll是一种I/O事件通知机制,是linux 内核实现IO多路复用的一个实现。
IO多路复用是指,在一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作。

I/O

输入输出(input/output)的对象可以是文件(file), 网络(socket),进程之间的管道(pipe)。在linux系统中,都用文件描述符(fd)来表示。

事件

  • 可读事件,当文件描述符关联的内核读缓冲区可读,则触发可读事件。
    (可读:内核缓冲区非空,有数据可以读取)
  • 可写事件,当文件描述符关联的内核写缓冲区可写,则触发可写事件。
    (可写:内核缓冲区不满,有空闲空间可以写入)

通知机制

通知机制,就是当事件发生的时候,则主动通知。通知机制的反面,就是轮询机制。

epoll的通俗解释

结合以上三条,epoll的通俗解释是一种当文件描述符的内核缓冲区非空的时候,发出可读信号进行通知,当写缓冲区不满的时候,发出可写信号通知的机制

epoll的API详解

epoll的核心是3个API,核心数据结构是:1个红黑树和1个链表

java面试题库精选_第2张图片

1. int epoll_create(int size)

功能:

  • 内核会产生一个epoll 实例数据结构并返回一个文件描述符,这个特殊的描述符就是epoll实例的句柄,后面的两个接口都以它为中心(即epfd形参)。

size参数表示所要监视文件描述符的最大值,不过在后来的Linux版本中已经被弃用(同时,size不要传0,会报invalid argument错误)

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

功能:

  • 将被监听的描述符添加到红黑树或从红黑树中删除或者对监听事件进行修改

typedef union epoll_data {
    void *ptr; /* 指向用户自定义数据 */
    int fd; /* 注册的文件描述符 */
    uint32_t u32; /* 32-bit integer */
    uint64_t u64; /* 64-bit integer */
} epoll_data_t;

struct epoll_event {
    uint32_t events; /* 描述epoll事件 */
    epoll_data_t data; /* 见上面的结构体 */
};

对于需要监视的文件描述符集合,epoll_ctl对红黑树进行管理,红黑树中每个成员由描述符值和所要监控的文件描述符指向的文件表项的引用等组成。

op参数说明操作类型:

  • EPOLL_CTL_ADD:向interest list添加一个需要监视的描述符
  • EPOLL_CTL_DEL:从interest list中删除一个描述符
  • EPOLL_CTL_MOD:修改interest list中一个描述符

struct epoll_event结构描述一个文件描述符的epoll行为。在使用epoll_wait函数返回处于ready状态的描述符列表时,

  • data域是唯一能给出描述符信息的字段,所以在调用epoll_ctl加入一个需要监测的描述符时,一定要在此域写入描述符相关信息
  • events域是bit mask,描述一组epoll事件,在epoll_ctl调用中解释为:描述符所期望的epoll事件,可多选。

常用的epoll事件描述如下:

  • EPOLLIN:描述符处于可读状态
  • EPOLLOUT:描述符处于可写状态
  • EPOLLET:将epoll event通知模式设置成edge triggered
  • EPOLLONESHOT:第一次进行通知,之后不再监测
  • EPOLLHUP:本端描述符产生一个挂断事件,默认监测事件
  • EPOLLRDHUP:对端描述符产生一个挂断事件
  • EPOLLPRI:由带外数据触发
  • EPOLLERR:描述符产生错误时触发,默认检测事件

3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

功能:

  • 阻塞等待注册的事件发生,返回事件的数目,并将触发的事件写入events数组中。

  • events: 用来记录被触发的events,其大小应该和maxevents一致

  • maxevents: 返回的events的最大个数

处于ready状态的那些文件描述符会被复制进ready list中,epoll_wait用于向用户进程返回ready list。events和maxevents两个参数描述一个由用户分配的struct epoll event数组,调用返回时,内核将ready list复制到这个数组中,并将实际复制的个数作为返回值。注意,如果ready list比maxevents长,则只能复制前maxevents个成员;反之,则能够完全复制ready list。
另外,struct epoll event结构中的events域在这里的解释是:在被监测的文件描述符上实际发生的事件。
参数timeout描述在函数调用中阻塞时间上限,单位是ms:

  • timeout = -1表示调用将一直阻塞,直到有文件描述符进入ready状态或者捕获到信号才返回;
  • timeout = 0用于非阻塞检测是否有描述符处于ready状态,不管结果怎么样,调用都立即返回;
  • timeout > 0表示调用将最多持续timeout时间,如果期间有检测对象变为ready状态或者捕获到信号则返回,否则直到超时。

epoll的两种触发方式

epoll监控多个文件描述符的I/O事件。epoll支持边缘触发(edge trigger,ET)或水平触发(level trigger,LT),通过epoll_wait等待I/O事件,如果当前没有可用的事件则阻塞调用线程。

select和poll只支持LT工作模式,epoll的默认的工作模式是LT模式。

1.水平触发的时机

  1. 对于读操作,只要缓冲内容不为空,LT模式返回读就绪。
  2. 对于写操作,只要缓冲区还不满,LT模式会返回写就绪。

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你。如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。

2.边缘触发的时机

  • 对于读操作
  1. 当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。
  2. 当有新数据到达时,即缓冲区中的待读数据变多的时候。
  3. 当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。
  • 对于写操作
  1. 当缓冲区由不可写变为可写时。
  2. 当有旧数据被发送走,即缓冲区中的内容变少的时候。
  3. 当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。

当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

在ET模式下, 缓冲区从不可读变成可读,会唤醒应用进程,缓冲区数据变少的情况,则不会再唤醒应用进程。

举例1:

  1. 读缓冲区刚开始是空的
  2. 读缓冲区写入2KB数据
  3. 水平触发和边缘触发模式此时都会发出可读信号
  4. 收到信号通知后,读取了1KB的数据,读缓冲区还剩余1KB数据
  5. 水平触发会再次进行通知,而边缘触发不会再进行通知

举例2:(以脉冲的高低电平为例)

  • 水平触发:0为无数据,1为有数据。缓冲区有数据则一直为1,则一直触发。
  • 边缘触发发:0为无数据,1为有数据,只要在0变到1的上升沿才触发。

JDK并没有实现边缘触发,Netty重新实现了epoll机制,采用边缘触发方式;另外像Nginx也采用边缘触发。

JDK在Linux已经默认使用epoll方式,但是JDK的epoll采用的是水平触发,而Netty重新实现了epoll机制,采用边缘触发方式,netty epoll transport 暴露了更多的nio没有的配置参数,如 TCP_CORK, SO_REUSEADDR等等;另外像Nginx也采用边缘触发。

epoll与select、poll的对比

1. 用户态将文件描述符传入内核的方式

  • select:创建3个文件描述符集并拷贝到内核中,分别监听读、写、异常动作。这里受到单个进程可以打开的fd数量限制,默认是1024。
  • poll:将传入的struct pollfd结构体数组拷贝到内核中进行监听。
  • epoll:执行epoll_create会在内核的高速cache区中建立一颗红黑树以及就绪链表(该链表存储已经就绪的文件描述符)。接着用户执行的epoll_ctl函数添加文件描述符会在红黑树上增加相应的结点。

2. 内核态检测文件描述符读写状态的方式

  • select:采用轮询方式,遍历所有fd,最后返回一个描述符读写操作是否就绪的mask掩码,根据这个掩码给fd_set赋值。
  • poll:同样采用轮询方式,查询每个fd的状态,如果就绪则在等待队列中加入一项并继续遍历。
  • epoll:采用回调机制。在执行epoll_ctl的add操作时,不仅将文件描述符放到红黑树上,而且也注册了回调函数,内核在检测到某文件描述符可读/可写时会调用回调函数,该回调函数将文件描述符放在就绪链表中。

3. 找到就绪的文件描述符并传递给用户态的方式

  • select:将之前传入的fd_set拷贝传出到用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。
  • poll:将之前传入的fd数组拷贝传出用户态并返回就绪的文件描述符总数。用户态并不知道是哪些文件描述符处于就绪态,需要遍历来判断。
  • epoll:epoll_wait只用观察就绪链表中有无数据即可,最后将链表的数据返回给数组并返回就绪的数量。内核将就绪的文件描述符放在传入的数组中,所以只用遍历依次处理即可。这里返回的文件描述符是通过mmap让内核和用户空间共享同一块内存实现传递的,减少了不必要的拷贝。

4. 重复监听的处理方式

  • select:将新的监听文件描述符集合拷贝传入内核中,继续以上步骤。
  • poll:将新的struct pollfd结构体数组拷贝传入内核中,继续以上步骤。
  • epoll:无需重新构建红黑树,直接沿用已存在的即可。

epoll更高效的原因

  1. select和poll的动作基本一致,只是poll采用链表来进行文件描述符的存储,而select采用fd标注位来存放,所以select会受到最大连接数的限制,而poll不会。
  2. select、poll、epoll虽然都会返回就绪的文件描述符数量。但是select和poll并不会明确指出是哪些文件描述符就绪,而epoll会。造成的区别就是,系统调用返回后,调用select和poll的程序需要遍历监听的整个文件描述符找到是谁处于就绪,而epoll则直接处理即可。
  3. select、poll都需要将有关文件描述符的数据结构拷贝进内核,最后再拷贝出来。而epoll创建的有关文件描述符的数据结构本身就存于内核态中,系统调用返回时利用mmap()文件映射内存加速与内核空间的消息传递:即epoll使用mmap减少复制开销。
  4. select、poll采用轮询的方式来检查文件描述符是否处于就绪态,而epoll采用回调机制。造成的结果就是,随着fd的增加,select和poll的效率会线性降低,而epoll不会受到太大影响,除非活跃的socket很多。
  5. epoll的边缘触发模式效率高,系统不会充斥大量不关心的就绪文件描述符

虽然epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

图文详解 epoll 原理【Redis,Netty,Nginx实现高性能IO的核心原理】epoll 详解 - 腾讯云开发者社区-腾讯云

39.请你说一下抽象类和接口的区别

Java中接口和抽象类的定义语法分别为interface与abstract关键字。

抽象类:在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:

a、抽象类不能被实例化只能被继承;

b、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;

c、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;

d、一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;

e、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。

接口Java中接口使用interface关键字修饰,特点为:

a、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);

b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;

c、一个类可以实现多个接口;

d、JDK1.8中对接口增加了新的特性:(1)、默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;(2)、静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)。

如下例子所示:

public interface Person{
  public static final int a=10;
  //JDK1.8
    default void sayHello(){
        System.out.println("Hello World");
    }
    public void say();
}
public abstract class Person{
  public abstract void say();
  public void eat(){};
}

如上述代码所示:

接口只能是功能的定义,而抽象类既可以为功能的定义也可以为功能的实现。

相同点

(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

不同点

(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。

(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。

(3)接口强调特定功能的实现,而抽象类强调所属关系。

(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

40.请你说说==与equals()的区别

一、对象类型不同

1、equals():是超类Object中的方法。

2、==:是操作符。

二、比较的对象不同

1、equals():用来检测两个对象是否相等,即两个对象的内容是否相等。

2、==:用于比较引用和比较基本数据类型时具有不同的功能,具体如下:

(1)、基础数据类型:比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。

(2)、引用数据类型:比较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。

三、运行速度不同

1、equals():没有==运行速度快。

2、==:运行速度比equals()快,因为==只是比较引用。

延伸:

equals()和==的源码定义:

public boolean equals(Object obj) {

return (this == obj);

}

由equals的源码可以看出这里定义的equals与==是等效的(Object类中的equals没什么区别),不同的原因就在于有些类(像String、Integer等类)对equals进行了重写。

但是没有对equals进行重写的类就只能从Object类中继承equals方法,其equals方法与==就也是等效的,除非在此类中重写equals。

对equals重新需要注意五点:

1、自反性:对任意引用值X,x.equals(x)的返回值一定为true;

2、对称性:对于任何引用值x,y,当且仅当y.equals(x)返回值为true时,x.equals(y)的返回值一定为true;

3、传递性:如果x.equals(y)=true, y.equals(z)=true,则x.equals(z)=true ;

4、 一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变;

5、非空性:任何非空的引用值X,x.equals(null)的返回值一定为false 。

==:

== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。

1、比较的是操作符两端的操作数是否是同一个对象。

2、两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。

3、比较的是地址,如果是具体的阿拉伯数字的比较,值相等则为true,如:

int a=10 与 long b=10L 与 double c=10.0都是相同的(为true),因为他们都指向地址为10的堆。

equals:

equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。

String s="abce"是一种非常特殊的形式,和new 有本质的区别。它是java中唯一不需要new 就可以产生对象的途径。

以String s="abce";形式赋值在java中叫直接量,它是在常量池中而不是象new一样放在压缩堆中。这种形式的字符串,在JVM内部发生字符串拘留,即当声明这样的一个字符串后,JVM会在常量池中先查找有有没有一个值为"abcd"的对象。

如果有,就会把它赋给当前引用.即原来那个引用和现在这个引用指点向了同一对象,如果没有,则在常量池中新创建一个“abcd"”,下一次如果有Strings1="abcd";又会将s1指向“abcd”这个对象,即以这形式声明的字符串,只要值相等,任何多个引用都指向同一对象。

而String s=new String("abcd”);和其它任何对象一样.每调用一次就产生一个对象,只要它们调用。

也可以这么理解:String str="hello”;先在内存中找是不是有“hello”这个对象,如果有,就让str指向那个“hello”。

如果内存里没有"hello",就创建一个新的对象保存"hello”.String str=new String(“hello")就是不管内存里是不是已经有"hello"这个对象,都新建一个对象保存"hello"。

41.说说synchronize的用法及原理

深入理解synchronized底层原理,一篇文章就够了! - 腾讯云开发者社区-腾讯云

42.说说你对AQS的理解

Java 并发高频面试题:聊聊你对 AQS 的理解? - 知乎

43.Java哪些地方使用了CAS

JAVA CAS实现原理与使用_装满哈希的Map的博客-CSDN博客_cas java

44.说说JVM的垃圾回收算法

JVM的垃圾回收机制——垃圾回收算法 - 知乎

45.请你说说Redis数据类型中的zset,它和set有什么区别?底层是怎么实现的?

Redis中hash、set、zset的底层数据结构原理 - 腾讯云开发者社区-腾讯云

46.说说static修饰符的用法

Java static静态修饰符详解:静态变量、静态方法及静态代码块

47.说说线程的状态

Java线程的6种状态及切换(透彻讲解)_潘建南的博客-CSDN博客_线程状态

48.说说你对ThreadLocal的理解

谈谈对ThreadLocal的理解_woaichihanbao的博客-CSDN博客

49.说说Spring Boot常用的注解

Spring Boot常用注解(绝对经典)_哪 吒的博客-CSDN博客_springboot常用注解

50.说说Bean的生命周期

面试题:Spring Bean的生命周期_ZhSuZhan的博客-CSDN博客_springbean的生命周期面试

51.synchronized和Lock有什么区别

5分钟搞清楚Synchronized和Lock的概念与区别 - 腾讯云开发者社区-腾讯云

52.说说volatile的用法及原理

深入理解Volatile关键字及其实现原理_平静的起步吧的博客-CSDN博客_volatile关键字原理

53.说说Redis的单线程架构

【Redis破障之路】三:Redis单线程架构_三分恶的博客-CSDN博客

54.如何实现Redis高可用

Redis 是如何实现高可用的?_Andrew.J的博客-CSDN博客

55.请你说一下final关键字

final关键字用法总结_老周聊架构的博客-CSDN博客_final关键字修饰方法

56.请你说说重载和重写的区别,构造方法能不能重写


1.方法重写(Override)
方法的重写主要体现在Java的继承体系中,子类出现了和父类一模一样的方法,方法重写也称为方法覆盖、方法复写。
方法重写的特点:

子类与父类的方法名和返回类型相同;
子类与父类方法的参数列表相同;
子类方法的访问范围不能比父类小;
返回值类型必须和被重写方法的类型相等或是其子类;
子类不能重写父类的构造方法,
2.方法重载(Overload)
在一个类中,有多个同名的方法,但是每个方法有不同的实现。

方法重载的特点:

方法名相同
方法的参数个数、参数类型不同
仅仅是返回值类型不同不能构成重载
构造方法可以重载
例:比如现在有一个eat 方法

//eat方法有两个参数
public void eat(Date time, String food){

    }
    //这种重载方式是可以的,参数的顺序不一致,也就认为是不同的参数
    public void eat(String food, Date time){

    }
    //这个就不行了,会认为是同一个方法
     public void eat(Date time1, String food1){

  }
  //仅仅是返回值类型不同是不能构成重载的,以下写法就会报错
  int f(int a, int b){
        return a + b;
    }
    boolean f(int a, int b){
        return false;
    }

为什么只有返回值类型不同不能构成重载?
在Java语言中,要重载一个方法,除了要与原方法具有相同的函数名称外,还要求要与原方法有一个不同的特征签名。特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,而返回值不会包含在所谓的特征签名之中,所以Java不允许只有返回值不同的重载。

为什么构造函数不能被重写?
首先我们要明白,重写的前提是要能被被继承。
构造函数的特点:

方法名和类名相等
无返回值类型
无返回值
根本无法满足函数重写条件。
另外假设可以重写父类构造器,那么子类名字就得和父类相等,显然这是不可能的,所以根本不可能重写父类构造方法。
 方法的重写和重载是实现Java多态的方式,重载一般称为编译时多态,重写通过继承实现,一般称为运行时多态。
 

57.请说说你对Java集合的了解

浅谈对java集合的理解_客驿风尘面的博客-CSDN博客

对JAVA的集合的理解 - huanshare - 博客园

58.请你说说IO多路复用

一文看懂IO多路复用 - 知乎

【面试】彻底理解 IO多路复用_Hollis Chuang的博客-CSDN博客

59.请你说说索引怎么实现的B+树,为什么选这个数据结构?

面试官:MySQL索引为什么要用B+树实现?_Java识堂的博客-CSDN博客_mysql索引为什么使用b+树

60.请你讲一下Java 8的新特性

Java8 新增了非常多的特性,我们主要讨论以下几个:

  • Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。

  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。

  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。

  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

  • Date Time API − 加强对日期与时间的处理。

  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。

61.请你说说泛型、泛型擦除

泛型使用与泛型擦除_情谊风月的博客-CSDN博客_泛型和泛型擦除

Java泛型中的类型擦除详解 - 知乎

62.说说你了解的线程通信方式

线程间的通信方式_LallanaLee的博客-CSDN博客_线程间的通信方式

63.请你说说JUC

juc简单理解_bluesmi的博客-CSDN博客_juc的了解

64.请你说说HashMap和Hashtable的区别

HashMap和Hashtable的区别_xuhuaabc的博客-CSDN博客_hashmap和hashtable

HashMap 和 Hashtable 有什么区别?_ConstXiong的博客-CSDN博客_hashmap和hashtable有什么区别

65.HashMap是线程安全的吗?如果不是该如何解决?

面试官:HashMap是线程安全的吗?有什么替代方案? - 知乎

hashmap是线程安全的吗?怎么解决?_【16期】你能谈谈HashMap怎样解决hash冲突吗_weixin_39681161的博客-CSDN博客

66.请你说说Java的四种引用方式

Java的四种引用方式_林老师带你学编程的博客-CSDN博客_java引用

67.请你讲下G1垃圾回收器

G1垃圾回收器详解 - 简书

G1垃圾回收器 - 知乎

68.请你说说内存溢出

面试题:什么是内存泄漏?内存溢出? - 知乎

69.请你说说内存泄漏

面试题:什么是内存泄漏?内存溢出? - 知乎

内存溢出怎么解决 内存溢出和内存泄露有哪些区别【详细介绍】-太平洋IT百科

70.请你说说数据库引擎有哪些,各自有什么区别

数据库常用的几种引擎,区别和比较_-Aurora的博客-CSDN博客_数据库引擎区别

MySQL有哪些数据库存储引擎,有什么区别?_永学者的博客-CSDN博客_mysql的存储引擎有哪些,区别是什么

71.简单介绍Spring

Spring简介、框架核心、优缺点、应用场景_ThinkWon的博客-CSDN博客_spring框架的优点

72.介绍一下MyBatis的缓存机制

MyBatis|缓存机制 - 简书

Mybatis缓存机制详解 - 知乎

73.请你说说String类,以及new和

java中String与new String的区别_成长的小牛233的博客-CSDN博客_java new string

74.请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()

面试官:重写 equals 时为什么一定要重写 hashCode? - 腾讯云开发者社区-腾讯云

75.说说线程的创建方式

线程创建的四种方式_愿好的博客-CSDN博客_创建线程的几种方式

线程的四种创建方式 - 知乎

76.说说你对ArrayList的理解

Java高频面试题:谈谈你对ArrayList的理解? - 知乎

77.请你说说BIO、NIO、O

说说BIO、NIO、AIO 的区别?_学一次的博客-CSDN博客_bio、nio和aio的区别

78.说说你对Spring Boot的理解,以及它和Spring的区别?

SpringBoot:简述SpringBoot和Spring的区别_王小二(海阔天空)的博客-CSDN博客_springboot和spring

面试官:小伙子,给我说一下Spring 和 Spring Boot 的区别吧 - 知乎

79.说说Spring Boot的自动装配

Spring Boot 自动装配原理与实现 - 知乎

80.说说@Autowired和@Resource注解的区别

@Autowired和@Resource注解的区别和联系(十分详细,不看后悔)_莫小兮丶的博客-CSDN博客_autowired和resource注解的区别

81.说说Redis的主从同步机制

说说Redis的同步机制?_ConstXiong的博客-CSDN博客_redis同步机制

82.说说Redis的缓存淘汰策略

深入理解redis——Redis的缓存过期淘汰策略 - SegmentFault 思否

83.说说垃圾收集器

JVM基础(五)垃圾收集器 - 知乎

84.请你说说Java的特点和优点,为什么要选择Java?

Java的特点和优点_学习者666的博客-CSDN博客_java的优点

85.介绍一下包装类的自动拆装箱与自动装箱

深入理解Java包装类与自动拆装箱_有头发的代码匠的博客-CSDN博客

86.说说wt()和sleep()的区别

sleep() 和 wait() 有什么区别? - 清晨的第一抹阳光 - 博客园

Java中sleep()和wait()的区别 - 简书

87.说说你对线程池的理解

每日一面——谈谈你对线程池的理解(上)_有理想的菜鸡的博客-CSDN博客

面试官 谈谈线程池的理解 - 知乎

88.简单说下你对JVM的了解

关于Jvm知识看这一篇就够了 - 知乎

89.说说Java运行时数据区

JAVA运行时数据区域 - fengbs - 博客园

90.请你讲下CMS垃圾回收器

CMS垃圾回收器详解_朱清震的博客-CSDN博客_cms垃圾回收器

91.说说JVM的双亲委派模型

JVM双亲委派机制详解 - 知乎

92.请你说说数据库索引的底层数据结构

数据库底层设计(MYSQL索引的数据结构) - 知乎

MySQL索引底层数据结构及原理深入分析_问北的博客-CSDN博客_mysql底层数据结构是什么

93.说说Spring Boot 的启动流程

SpringBoot-启动流程 - 楠予 - 博客园

94.介绍一下Spring MVC的执行流程

SpringMVC的执行流程以及运行原理__springmvc原理

95.在MyBatis中$和#有什么区别

面试题:Mybatis中 $ 和 # 的区别?_>no problem<的博客-CSDN博客

96.请你说说Java基本数据类型和引用类型

Java基础数据类型和引用类型的区别 - 腾讯云开发者社区-腾讯云

97.请你说说Java的异常处理机制

Java的异常处理机制_BoSeal的博客-CSDN博客_java异常处理机制

98.说说你对面向对象的理解

面向对象思想--谈谈你对面向对象的理解_一海星辰_1997的博客-CSDN博客_怎么理解面向对象

99.请介绍一下访问修饰符

访问修饰符(总结完整)_Laity(俗人)!的博客-CSDN博客

100.说说Java中常用的锁及原理

Java 中的各种锁及其原理_Pan's pidr的博客-CSDN博客_java锁的原理

101.请你说说List与Set的区别

List和Set的区别_Star_Gemini的博客-CSDN博客_list set

102.请你讲一下Java NIO

Java NIO?看这一篇就够了!_归海一刀之渔舟唱晚的博客-CSDN博客_nio

103.说说GC的可达性分析

GC 可达性分析算法_谭谈谈的博客-CSDN博客_gc可达性分析

104.说说类的实例化过程

类的加载和实例化过程_doforfuturedx的博客-CSDN博客_类实例化的过程

105.请你讲讲B树和B+树

B+树和B树的区别 - 简书

106.MySQL主从同步是如何实现的?

Mysql主从同步的实现原理_OkidoGreen的博客-CSDN博客_mysql主从同步原理

107.请你介绍一下数据库的ACID

数据库ACID_hengliang_的博客-CSDN博客_数据库acid

108.请你说说数据库的索引是什么结构,为什么不用哈希表

为什么mysql索引用B+树而不用哈希表?_JackMa_的博客-CSDN博客

109.请你说说InnoDB的MVCC

InnoDB的MVCC实现原理(InnoDB如何实现MVCC以及MVCC的工作机制)_生而知之者为妖的博客-CSDN博客_innodb mvcc

110.说说Spring Boot的起步依赖

Spring Boot起步依赖和自动配置_sscout的博客-CSDN博客_springboot起步依赖与自动配置

111.说说Spring事务管理

Spring事务管理详解_GrayHJX的博客-CSDN博客_spring事务管理

113.说说Bean的作用域,以及默认的作用域

Spring bean 的5个作用域 Spring Bean的默认作用域为:singleton。它相比其他作用域的优点是系统开销小,Bean实例一旦创建成功便可重复使用。 1.singleton作用域_门里有什么的博客-CSDN博客_spring默认作用域

114.说说BeanFactory和FactoryBean的区别

BeanFactory和FactoryBean的区别_wangbiao007的博客-CSDN博客_beanfactory和factorybean

115.说说你对Redis的了解

Redis 简介 | 菜鸟教程

你可能感兴趣的:(java学习笔记,java,面试,redis)