面试题汇总

JAVA 基础

1.java 性能优化

①尽量指定类、方法的final 修饰符

②尽量重用对象

③尽可能使用局部变量

④及时关闭流

⑤尽量减少对变量的重复计算

⑥尽量采用懒加载的策略,即在需要的时候才创建

⑦不要在循环中使用try…catch…,应该把其放在最外层

2.字符串String 类和基本的数据类型有什么区别

基本数据类型:变量名指向具体的数值

引用数据类型:变量名指向存数据对象的内存地址,即变量名指向hash 值

3.Java 反射

意义:增加程序的灵活性,避免将程序写死到代码里。

优点:①反射提高了Java 程序的灵活性和扩展性,降低耦合性,提高自适应能力

②代码简洁,提高代码的复用率,外部调用方便

③对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法

缺点:性能问题、使用反射会模糊程序内部逻辑、安全限制、内部暴露

4.start ()和run ()方法的区别

1 )start :

用start 方法来启动线程,真正实现了多线程运行,这时无需等待run 方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread 类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu 时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run 方法运行结束,此线程随即终止。

2 )run :

run()方法只是类的一个普通方法而已,如果直接调用Run 方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run 方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

5.sleep(休眠)和wait(等待)的区别

区别1 :使用限制

使用 sleep 方法可以让让当前线程休眠,时间一到当前线程继续往下执行,在任何地方都能使用,但需要捕获InterruptedException 异常。

区别2 :使用场景

sleep 一般用于当前线程休眠,或者轮循暂停操作,wait 则多用于多线程之间的通信。

区别3 :所属类

sleep 是Thread 类的静态本地方法,wait 则是Object 类的本地方法。

6.多线程情况下保证数据一致的方法

使用synchronized 关键字

比如:对num 变量进行操作,如果没有synchronized 关键字,即使是使用volatile 修饰变量,输出的值也会小于100000 ,因为volatile 虽然能够保证可见性及顺序性,但是不能保证变量的原子性。

7.Java String 、StringBuffer 和StringBuilder 的区别

String :字符串常量,字符串长度不可变。Java 中String 是immutable (不可变)的。

StringBuffer :字符串变量(Synchronized ,即线程安全)。如果要频繁对字符串内容进行修改,出于效率考虑最好使用StringBuffer ,如果想转成String 类型,可以调用StringBuffer 的toString()方法。

StringBuilder :字符串变量(非线程安全)。在内部,StringBuilder 对象被当作是一个包含字符序列的变长数组。

8.spring 的ioc 和依赖注入和aop 的原理

IOC (控制权反转):

控制反转. 也成为DI(依赖注入)其思想是反转 资源获取的方向.传统的资源查找方式要求组件向容器发起请求查找资源.作为 回应,容器适时的返回资源.而应用了IOC 之后,则是容器主动地将资源推送 给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源.这种行为也被称为查找的被动形式。

DI (依赖注入)

控制翻转(Inversion of Control-IOC )和依赖注入(dependency injection-DI )在Spring 环境下是同等的概念,控制翻转是通过依赖注入实现的。所谓依赖注入是指容器负责创建对象和维护对象间的依赖关系,而不是通过对象本身去创建和解决自自己的依赖。依赖的主要目的是为了解耦,体现一种”组合”的理念。

AOP (面向切面编程)

面向(方面)切面的编程;Filter(过滤器)也是一种AOP. AOP 是一种新的方法论,是对传统OOP(Object-Oriented Programming,面向对象编程)的补充. AOP 的主要编程对象是切面(aspect),而切面模块化横切关注点.可以举例通过事务说明。

9.Java 线程池七个参数

①corePoolSize 线程池核心线程大小

②maximumPoolSize 线程池最大线程数量

③keepAliveTime 空闲线程存活时间

④unit 空闲线程存活时间单位

⑤workQueue 工作队列

⑥threadFactory 线程工厂

⑦handler 拒绝策略

10.什么是乐观锁,什么是悲观锁

乐观锁:

乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量.

悲观锁:

当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制【Pessimistic Concurrency Control ,缩写”PCC”,又名”悲观锁”】。

悲观锁,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。

11.java 创建线程的方法

①通过实现Runnable 接口

②通过继承Thread 接口

③通过Callable 和Future 创建线程

相同点:

①都是接口

②都可以编写多线程程序

③都采用Thread.start()启动线程

不同点:

①Callable 规定的方法是call(),Runnable 规定的方法是run()。其中Runnable 可以提交给Thread 来包装下,直接启动一个线程来执行,而Callable 则一般都是提交给ExecuteService 来执行。

②Callable 的任务执行后可返回值,而Runnable 的任务是不能返回值得

③call 方法可以抛出异常,run 方法不可以

④运行Callable 任务可以拿到一个Future 对象,c 表示异步计算的结果。

12.sql 语句中#{}和${}的区别

{}:预编译

${}:替换字符串

13.ResultType 和ResultMap 的区别

ResultType :

ResultType 相对与ResultMap 而言更简单一点。只有满足ORM (Object Relational Mapping ,对象关系映射)时,即数据库表中的字段名和实体类中的属性完全一致时,才能使用,否则会出现数据不显示的情况。如图所示,由于实体类Order 的属性和表tb_order 的字段不一致,导致页面数据不显示。

ResultMap :

ResultMap 和ResultType 的功能类似,但是ResultMap 更强大一点,ResultMap 可以实现将查询结果映射为复杂类型的pojo 。

14.多线程加锁的方式

①NSLock NSLock 是Cocoa 提供给我们最基本的锁对…

②synchronized (互斥锁)synchronized …

③atomic atomic 只是给成员变量的set 和get 方法…

④OSSpinlock 自旋锁 耗时最少 自旋锁几乎不进入内核…

⑤pthread_mutex 是底层的API ,在各种加锁方式中…

⑥NSConditionLock (条件锁) 条件锁与特定的与用…

⑦NSRecursiveLock 递归锁NSRecursive …

15. @ResponseBody 和@RequestBody 注解的区别

@Responsebody 注解:表示该方法的返回的结果直接写入HTTP 响应正文中,一般在异步获取数据时使用;

@RequestBody 注解:则是将HTTP 求正文插入方法中,使用适合的HttpMessageConverter 将请求体写入某个对象。

16.java 运行时异常和抛出异常的区别

与张航日常交流的心得,

当在写事务回滚的时候,

我们会利用异常来执行是否提交和回滚。

异常分为运行时异常与受查异常。

运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如,

运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代

码编写足够仔细,程序足够健壮,运行时异常是可以避免的。

受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,

则无法通过编译。如 SQLException ,ClassNotFoundException ,IOException 等都属于受查异常。

RuntimeException 及其子类以外的异常,均属于受查异常。当然,用户自定义的Exception

的子类,即用户自定义的异常也属受查异常。程序员在定义异常时,只要未明确声明定义的

为 RuntimeException 的子类,那么定义的就是受查异常。

17.Java 中异常分为哪两大类

java 中异常分类:最先是Object

Object 下有Throwable

Throwable 下有两个分支:Error (不可处理,直接退出JVM )和Exception (可处理的)

Exception 下有两个分支:Exception 直接子类,RuntimeException 。

Exception 的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,不处理,编译不通过)

编译时异常也被称为:受检异常(CheckedException )、受控异常。

RuntimeException :运行时异常(在编写程序阶段程序员可以处理,也可以不处理)。

运行时异常也被称为:未受检异常(UnCheckedException )、非受控异常。

18.字符串常量池

字符串常量池的设计思想

1 、字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。

2 、JVM 为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。

1 )为字符串开辟一个字符串常量池,类似于缓存区。

2 )创建字符串常量时,首先坚持字符串常量池是否存在该字符串。

3 )存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中。

3 、实现的基础

1 )实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享。

2 )运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收。

19.什么是事务?事务的作用是什么?

事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);

事务的四大特性:

1 、原子性

事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

2 、一致性

事 务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。

3 、隔离性

一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

4 、持续性

也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

20.Java 的四种引用方式

①强引用:是指创建一个对象并把这个对象赋给一个引用变量。

②.软引用(SoftReference ):如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;

如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

③弱引用也是用来描述非必需对象的,当JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java 中,用java.lang.ref.WeakReference 类来表示。

④虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java 中用java.lang.ref.PhantomReference 类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

Spring

spring是一个轻量级的框架,由很多个模块组成,提高开发效率以及系统的维护性。

spring管理事务的方式有几种?

1.声明式事务:在配置文件中配置 (又分为基于XML的声明式 / 基于注解的声明式事务@Transactional)

2.编程式事务:在代码中编码(不推荐)

spring的注入方式有哪几种?

Settr()方法注入 / 构造器注入 / 接口的注入(方法的注入)

简述AOP 和 IOC 概念 AOP

AOP:面向(方面)切面的编程;Filter(过滤器) 也是一种 AOP. AOP 是一种新的方

法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充. AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.可以举例通过事务说明。

IOC:Invert Of Control, 控制反转. 也成为 DI(依赖注入)其思想是反转 资源获取的方向. 传统的资源 查找方式要求组件向容器发起请求查找资源.作为 回应, 容器适时的返回资源. 而应用了 IOC 之后, 则 是容器主动地将资源推送 给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源. 这种行为也被称为查找的被动形式。

日志记录,性能统计,安全控制,事务处理,异常处理等

SpringBoot

springboot 读取配置文件的几种方式

@PropertySource :指定配置文件路径(帕布利汇文思)

@Value :用于读取单个属性值

@ConfigurationProperties读取配置文件属性(看飞哥陪审帕布利)

spring boot在项目中的作用

1.简化了许多的配置文件

2.它内部整合了许多框架以及

spring boot的执行流程

①.首先新建一个SpringApplication对象

1.获取webApplicationType 类型

2.注入ApplicationContextInitializer初始化器

3.设置监听器ApplicationListener

②.执行对象的run方法

③.对配置的启动类所在包及子包中的类进行扫描,对于有spring相关注解的类,通过反射为其创建代理对象,并交由spring容器管理

spring boot的核心实现

@SpringBootApplication

①@Configuration:表示该类是spring boot的配置类

②@EnableAutoConfiguration:程序启动时,自动加载spring boot默认的配置

③@ComponentScan:程序启动时,它会去扫描当前包及子包下所有的类

对mvc 的了解:MVC是一种设计模式,是集成spring开发的一款springMVC框架,可以帮助我们进行web层的开发。

优点:Spring框架提供了构建web应用程序的MVC模块

1.清晰的角色划分:像控制器controller….DispathServlet前端控制器等每一个角色对应一个专门的对象来实现

2.支持各种请求资源的映射;

3.简单强大的JSP标签库:支持数据绑定和主题之类的许多功能。

4.强大而直接的配置方式:框架和应用程序类都能作为JavaBean配置,支持跨多个context的引用

springmvc的执行流程

01、用户发送出请求到前端控制器DispatcherServlet。

02、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

03、HandlerMapping找到具体的控制器(可查找xml配置或注解配置),生成处理器对象的执行链(如果有),再一起返回给DispatcherServlet。

04、DispatcherServlet调用HandlerAdapter(处理器适配器)。

05、HandlerAdapter经过适配调用具体的处理器(controller)。

06、Controller执行完成返回ModelAndView对象。

07、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。

08、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。

09、ViewReslover解析后返回具体View(视图)。

10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

11、11、DispatcherServlet响应用户。

Mysql

MySQL 如何优化

①表的设计合理化(符合 3NF)

②添加适当索引(index) [四种: 普通索引、主键索引、唯一索引 unique、全文索引]

③SQL 语句优化

④分表技术(水平分割、垂直分割)

⑤读写[写: update/delete/add]分离

⑥存储过程 [模块化编程,可以提高速度]

⑦对mysql 配置优化 [配置最大并发数 my.ini, 调整缓存大小 ]

⑧mysql 服务器硬件升级

⑨定时的去清除不需要的数据,定时进行碎片整理(MyISAM)

事物的四大特性

①原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么全部不执行;

②一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;

③隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

④持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

SQL优化的几种方式

1.对查询进行优化,尽量避免全表扫描,考虑在order by及where涉及的列上创建索引

2.避免在where子句中对字段进行null值判断,否则会导致引擎放弃索引进行全表扫描(给这个列默认值)

3.尽量不要使用*(select * from t),用具体的字段列表来代替

4.避免在where中使用or 来连接条件,也不要使用!= 这些,否则会放弃使用索引导致全表扫描……

数据库优化的几种方式

为什么要进行数据库的优化?提高系统的吞吐量,数据量多的时候提高查询效率

1.选取最适用的字段属性(定义邮政编码的时候,不要使用默认值大小,选择合适的字段大小)

2.使用连接(JOIN)代替子查询

3.在操作的SQL一系列执行的时候,避免哪条出现问题带来的数据破坏,使用事务来保证数据的一致性和完整性

4.建立索引来提高它的一个查询效率

MySQL都有哪些锁

从锁的类别上来说:共享锁和排他锁

共享锁:又叫做读锁。当用户进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个

排他锁:又叫做写锁。当用户进行数据的写入时,对数据加上排他锁。排他锁只有一个,它与其他的排他锁和共 享锁互斥,保证数据的安全性。

行锁:行锁是各个引擎会自己实现的,但是像MyISAM是不支持行锁的。就比如A线程在对这行数据进行修改,此时B线程要等待A释放之后才能进行操作。它不是不需要了就立即释放,而是要等事务提交之后才会释放。

Redis

什么是Redis?

Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存亦可持久化的 日志型、Key-Value 数据库,并提供多种语言的 API 的非关系型数据库。

Redis 支持的数据类型?

①String :最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。

②hash :这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,

就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出

类似session的效果。

list :使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做

基于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生

产者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。

set :因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行

去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,

再起一个公共服务,太麻烦了。另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜

好,自己独有的喜好等功能。

⑤sorted set :sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操 作。

Redis 常用命令

①Keys pattern :*表示区配所有

②Set :设置 key 对应的值为 string 类型的 value。

③setnx :设置 key 对应的值为 string 类型的 value。如果 key 已经存在,返回 0,nx 是 not exist 的 意思。

④Expire 设置过期时间(单位秒)

TTL查看剩下多少时间:返回负数则key失效,key不存在了

⑤Setex :设置 key 对应的值为 string 类型的 value,并指定此键值对应的有效期。

⑥Mset :一次设置多个 key 的值,成功返回 ok 表示所有的值都设置了,失败返回 0 表示没有任何值被设 置。

⑤persist xxx(取消过期时间)

⑥Select 0 //选择数据库

⑦move age 1//把age 移动到1库

⑧Randomkey随机返回一个key

⑨Rename重命名

⑩Type 返回数据类型

八种基本数据类型是什么?他们的包装类型是什么?各占多少个字节?

byte Byte 1个字节、short Short 2个字节、int Integer 4个字节、long Long 8个字节、float Float 4个字节、double Double 8个字节、char Character 2个字节、boolean Boolean 1位

==与equals的区别

==比较的是地址,equals比较的是内容

重载和重写的区别

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。

重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为private则子类就不能重写该方法。

ArrayList 和 linkedList 的区别

Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的。

LinkList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些 对比都是指数据量很大或者操作很频繁。

HashMap 和 HashTable 的区别

1、继承的父类不同

HashMap继承自AbstractMap类。但二者都实现了Map接口。(啊不思抓美呸)

Hashtable继承自Dictionary类,Dictionary类是一个已经被废弃的类。父类都被废弃,自然而然也没人用它的子类Hashtable了。(待看圣而立)

2、HashMap线程不安全,HashTable线程安全

3 .是否允许null值

Hashmap是允许key和value为null值的,用containsValue(康泰因思)和containsKey方法判断是否包含对应键值对;HashTable键值对都不能为空,否则包空指针异常。

使用 max(id) ; selectkey 使用 @@identity (赛勒克特艾登特提)

select @@IDENTITY

@RestController 注解,相当于@Controller+@ResponseBody 两个注解的结合,返回json 数据不需要在方法前面加@ResponseBody 注解了,但使用@RestController 这个注解,就不能返回jsp,html 页面,视图解析器无法解析jsp,html 页面

  1. 写入时候要求启用事务处理,保证写一定成功。

  2. redis配置成任何变更一定实时持久化,比如存储端是磁盘的话,每次变更马上同步写入磁盘,才算完成。redis是支持这种方式配置的,但是这么做会使它的内存数据库特性完全消失,性能变得十分低下。

  3. 消费端也要实现事务方式,处理完成后,再回来真实删除消息。

  4. 多线程或者多端同时并发处理,可以通过锁的方式来规避。

spring 的核心是IOC 和AOP (Aspect Oriented Programming )。其中ioc 是将对象创建权交由spring 容器(这个一带而过,接着说aop ),AOP 是【面向切面编程】,使用【动态代理】技术,实现在【不修改java 源代码】的情况下,运行时实现方法功能的【增强】,而动态代理内部实现用的是反射。spring 的事务是通过aop 来实现的,从cglib.jar 包就可以看出,是和动态代理相关的。

2.spring 事务的使用

声明式和编程式(用的比较少)

声明式事务: 建立在AOP 之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明即可。

3.spring 的事务四个基本特性(ACID 原则)

原子性(Atomicity ):

一个事务已经是一个不可再分割的工作单位。事务中的全部操作要么都做;要么都不做

一致性(Consistency):

事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。

隔离性(Isolation):

事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性 和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。

持久性(Durability):

一个事务一旦提交,它对数据库中数据的改变会永久存储起来。其他操作不会对它产生影响

垃圾回收算法

标记-清除算法

标记-清除(Tracing Collector )算法是最基础的收集算法,为了解决引用计数法的问题而提出。它使用了根集的概念,它分为”标记”和”清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的可达性分析法中判定垃圾对象的标记过程。

优点:不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效。

缺点:标记和清除过程的效率都不高,这种方法需要使用一个空闲列表来记录所有的空闲区域以及大小,对空闲列表的管理会增加分配对象时的工作量;标记清除后会产生大量不连续的内存碎片,虽然空闲区域的大小是足够的,但却可能没有一个单一区域能够满足这次分配所需的大小,因此本次分配还是会失败,不得不触发另一次垃圾收集动作

标记-整理算法

标记-整理(Compacting Collector )算法标记的过程与”标记-清除”算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。在基于”标记-整理”算法的收集器的实现中,一般增加句柄和句柄表。

优点:经过整理之后,新对象的分配只需要通过指针碰撞便能完成,比较简单;使用这种方法,空闲区域的位置是始终可知的,也不会再有碎片的问题了。

缺点:GC 暂停的时间会增长,因为你需要将所有的对象都拷贝到一个新的地方,还得更新它们的引用地址

基本定义:

left join (左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。

right join (右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。

inner join (等值连接或者叫内连接):只返回两个表中连接字段相等的行。full join (全外连接):返回左右表中所有的记录和左右表中连接字段相等的记录。

读写锁可以有三种状态:读模式下加锁、写模式加锁、不加锁状态。一次可以只有一个线程占有读写锁的写模式,但是多个线程可以同时占用读模式的读写锁。当读写锁是写加锁状态时,在这个锁解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁的线程都会阻塞,直到所有的线程释放他们的写锁为止。读写锁适用于对数据的读远大于写的情况。

自旋锁和互斥量类似,忙等(自旋)阻塞,而不是休眠阻塞。自旋锁适用于持有的时间端,而且线程并不希望在重新调度上花费太多的成本。多用于内核态,用户态用的比较少。

死锁产生的原因及解决方法

原因: 指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进 ab 线程互相持有对方的资源且得不到释放就会产生死锁

解决方法:

加锁顺序(线程按照一定的顺序加锁)

加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

死锁检测

数据去重:

对于数据去重,一般通过duplicated()和 drop_duplicates()函数相结合进行实现。自己也是菜鸟,大家有什么好的方法可以一起讨论。(抓_读破k 特)

其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

2.Nginx 作为 web 服务器

Nginx 可以作为静态页面的 web 服务器,同时还支持 CGI 协议的动态语言,比如 perl、php 等。但是不支持 java。Java 程序只能通过与 tomcat 配合完成。Nginx 专为性能优化而开发, 性能是其最重要的考量,实现上非常注重效率 ,能经受高负载的考验

  1. 正向代理

Nginx 不仅可以做反向代理,实现负载均衡。还能用作正向代理来进行上网等功能。 正向代理:如果把局域网外的 Internet 想象成一个巨大的资源库,则局域网中的客户端要访 问 Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理。

简单一点:通过代理服务器来访问服务器的过程 就叫 正向代理。

需要在客户端配置代理服务器进行指定网站访问

  1. 反向代理

反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问。

我们只 需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返 回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器 地址,隐藏了真实服务器 IP 地址。

  1. 负载均衡

增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的 情况改为将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们所说的负 载均衡

客户端发送多个请求到服务器,服务器处理请求,有一些可能要与数据库进行交互,服 务器处理完毕后,再将结果返回给客户端。

6.动静分离

为了加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速 度。降低原来单个服务器的压力。

MySQL

MySQL 如何调优

一.创建索引

1.要尽量避免全表扫描,首先应考虑在where 及order by 涉及的列上建立索引

2.(1)在经常需要进行检索的字段上创建索引,比如要按照表字段username 进行检索,那么就应该在姓名字段上创建索引,如果经常要按照员工部门和员工岗位级别进行检索,那么就应该在员工部门和员工岗位级别这两个字段上创建索引。

(2)创建索引给检索带来的性能提升往往是巨大的,因此在发现检索速度过慢的时候应该首先想到的就是创建索引。

(3)一个表的索引数最好不要超过6 个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。索引并不是越多越好,索引固然可以提高相应的select 的效率,但同时也降低了insert 及update 的效率,因为insert 或update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。

二.避免在索引上使用计算

在where 字句中,如果索引列是计算或者函数的一部分,DBMS 的优化器将不会使用索引而使用全表查询,函数

属于计算的一种,同时在in 和exists 中通常情况下使用EXISTS ,因为in 不走索引

效率低:

select * from user where salary*22>11000(salary 是索引列)

效率高:

select * from user where salary>11000/22(salary 是索引列)

三.使用预编译查询

程序中通常是根据用户的输入来动态执行SQL ,这时应该尽量使用参数化SQL,这样不仅可以避免SQL 注入漏洞

攻击,最重要数据库会对这些参数化SQL 进行预编译,这样第一次执行的时候DBMS 会为这个SQL 语句进行查询优化

并且执行预编译,这样以后再执行这个SQL 的时候就直接使用预编译的结果,这样可以大大提高执行的速度。

四.调整Where 字句中的连接顺序

DBMS 一般采用自下而上的顺序解析where 字句,根据这个原理表连接最好写在其他where 条件之前,那些可以

过滤掉最大数量记录。

五.尽量将多条SQL 语句压缩到一句SQL 中

每次执行SQL 的时候都要建立网络连接、进行权限校验、进行SQL 语句的查询优化、发送执行结果,这个过程

是非常耗时的,因此应该尽量避免过多的执行SQL 语句,能够压缩到一句SQL 执行的语句就不要用多条来执行。

六.用where 字句替换HAVING 字句

避免使用HAVING 字句,因为HAVING 只会在检索出所有记录之后才对结果集进行过滤,而where 则是在聚合前

刷选记录,如果能通过where 字句限制记录的数目,那就能减少这方面的开销。HAVING 中的条件一般用于聚合函数

的过滤,除此之外,应该将条件写在where 字句中。

七.使用表的别名

当在SQL 语句中连接多个表时,请使用表的别名并把别名前缀于每个列名上。这样就可以减少解析的时间并减

少哪些友列名歧义引起的语法错误。

八.用union all 替换union

当SQL 语句需要union 两个查询结果集合时,即使检索结果中不会有重复的记录,如果使用union 这两个结果集

同样会尝试进行合并,然后在输出最终结果前进行排序,因此如果可以判断检索结果中不会有重复的记录时候,应

该用union all ,这样效率就会因此得到提高。

九.考虑使用”临时表”暂存中间结果

简化SQL 语句的重要方法就是采用临时表暂存中间结果,但是,临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb 中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中”共享锁”阻塞”更新锁”,减少了阻塞,提高了并发性能。

但是也得避免频繁创建和删除临时表,以减少系统表资源的消耗。

十.只在必要的情况下才使用事务begin translation

SQL Server 中一句SQL 语句默认就是一个事务,在该语句执行完成后也是默认commit 的。其实,这就是begin tran 的一个最小化的形式,好比在每句语句开头隐含了一个begin tran ,结束时隐含了一个commit 。

有些情况下,我们需要显式声明begin tran ,比如做”插、删、改”操作需要同时修改几个表,要求要么几个表都修改成功,要么都不成功。begin tran 可以起到这样的作用,它可以把若干SQL 语句套在一起执行,最后再一起commit 。 好处是保证了数据的一致性,但任何事情都不是完美无缺的。Begin tran 付出的代价是在提交之前,所有SQL 语句锁住的资源都不能释放,直到commit 掉。

可见,如果Begin tran 套住的SQL 语句太多,那数据库的性能就糟糕了。在该大事务提交之前,必然会阻塞别的语句,造成block 很多。

Begin tran 使用的原则是,在保证数据一致性的前提下,begin tran 套住的SQL 语句越少越好!有些情况下可以采用触发器同步数据,不一定要用begin tran 。

十一.尽量避免使用游标

尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。因为游标的效率较差,如果游标操作的数据超过1 万行,那么就应该考虑改写。

十二.用varchar/nvarchar 代替char/nchar

尽可能的使用 varchar/nvarchar 代替char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

不要以为 NULL 不需要空间,比如:char(100)型,在字段建立时,空间就固定了, 不管是否插入值(NULL 也包含在内),都是占用100 个字符的空间的,如果是varchar 这样的变长字段,null 不占用空间。

十三.查询select 语句优化

1.任何地方都不要使用select * from t ,用具体的字段列表代替”*”,不要返回用不到的任何字段

2.应尽量避免在where 子句中对字段进行null 值判断,否则将导致引擎放弃使用索引而进行全表扫描

4.不能前置百分

十四.更新Update 语句优化

1.如果只更改1 、2 个字段,不要Update 全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志

十五.删除Delete 语句优化语句

1.最高效的删除重复记录方法(因为使用了ROWID)例子:

DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID) FROM EMP X WHERE X.EMP_NO = E.EMP_NO);

十六.插入Insert 语句优化

1.在新建临时表时,如果一次性插入数据量很大,那么可以使用select into 代替create table ,避免造成大量log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table ,然后insert 。

MySQL 索引的优化/如何避免索引失效

遵循最左匹配原则:指的是查询从索引的最左前列开始并且不跳过索引中的列。Mysql 查询优化器会对查询的字段进行改进,判断查询的字段以哪种形式组合能使得查询更快,所有比如创建的是(a,b)索引,查询的是(b,a),查询优化器会修改成(a,b)后使用索引查询。

不在索引列上做任何操作

计算:对索引进行表达式计算会导致索引失效,如 where id + 1 = 10 ,可以转换成where id = 10 -1 ,这样就可以走索引

函数:select * from t_user where length(name)=6;此语句对字段使用到了函数,会导致索引失效

(自动/手动)类型转换

-(字符串类型必须带''引号才能使索引生效)字段是varchar ,用整型进行查询时,无法走索引

-字段是int ,用string 进行查询时,mysql 会自动转化,可以走索引

存储引擎不能使用索引中范围条件右边的列:如这样的sql: select * from user where username=’123′ and age>20 and phone=’1390012345′,其中username, age, phone 都有索引,只有username 和age 会生效,phone 的索引没有用到。

尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致))

mysql 在使用负向查询条件(!=、<>、not in 、not exists 、not like)的时候无法使用索引会导致全表扫描

is null, is not null 也无法使用索引,在实际中尽量不要使用null (避免在where 子句中对字段进行null 值判断)

like 以通配符开头(%abc..)时,mysql 索引失效会变成全表扫描的操作

少用or ,在WHERE 子句中,如果在OR 前的条件列是索引列,而在OR 后的条件列不是索引列,那么索引会失效

索引的类型、概念和使用场景

1 ).什么是索引

索引是一种数据结构。数据库索引是数据库管理中一个排序的数据结构,以协助快速查询,更新数据库表数据。索引通常使用B 树及其变种B+树。

更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。

2 )索引有哪些优缺点

1.优点:可以加快数据的查询速度 提高系统的性能

2.缺点:创建索引和维护索引需要耗费时间,当对表中数据进行增加,删除和修改时也要进行多态维护,会降低执行效率/需要占物理空间

3 )索引的使用场景

where

尝试在一个字段未建立索引时,根据字段查询的效率,然后对该字段建立索引(alter table 表名add index(字段名)),同样的SQL 执行的效率,会发现查询效率会有很明显的提升(数据量越大越明显)

Order by

当我们使用order by 将查询结果按照某个字段排序时,如果该字段没有建立索引,那么执行计划会将查询出的所有数据使用外部排序(将数据从硬盘分批读取到内存使用内部排序,最后合并排序结果),这个操作是很影响性能的。

但是如果我们对该字段建立索引alter table 表名add index(字段名),那么由于索引本身是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可。而且如果分页的,那么只用取出索引表某个范围内的索引对应的数据,而不用像上述那取出所有数据进行排序再返回某个范围内的数据。(从磁盘取数据是最影响性能的)

索引覆盖

如果要查询的字段都建立过索引,那么引擎会直接在索引表中查询而不会访问原始数据(否则只要有一个字段没有建立索引就会做全表扫描),这叫索引覆盖。因此我们需要尽可能的在select 后只写必要的查询字段,以增加索引覆盖的几率。

这里值得注意的是不要想着为每个字段建立索引,因为优先使用索引的优势就在于其体积小。

4 )索引有哪几种类型?

1.主键索引:数据列不允许重复,不允许为NULL ,一个表只能有一个主键

2.唯一索引:数据列不允许重复,允许为NULL ,一个表允许多个列创建唯一索引


可以通过 ALTER TABLE table_name ADD UNIQUE (column);创建唯一索引

//unique

可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2);创建唯一组合索引

3.普通索引:基本索引,没有唯一性的限制,允许为null 值


可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引

可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引

4.全文索引:是目前搜索引擎使用的一种关键技术


可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引//fulltext

5 )联合索引(复合)

联合索引是两个或更多个列上的索引。对于联合索引:Mysql 从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c).可以支持a 、a,b 、a,b,c 3 种组合进行查找,但不支持b,c 进行查找.当最左侧字段是常量引用时,索引就十分有效。

MySQL 中使用多个字段同时建立一个索引叫做联合索引。使用联合索引需要按照索引时的字段顺序来挨个使用,否则无法命中索引。MySQL 使用索引时需要索引有序,假设现在建立了”name ,age ,school”的联合索引,那么索引的排序为:先按照name 排序,如果name 相同,则按照age 排序,如果age 的值也相等,则按照school 进行排序。

6 )什么情况下不适合建立索引

1.表记录数据量少的适合

2.经常插入,删除,修改的表不适合

3.数据重复的字段也不适合建立索引……

MySQL 关联多个表的查询

内连接

  • select *from 表1 join 表2 [ on 过滤条件] [ where 查询条件];

  • select *from 表1 ,表2 [ where 查询条件];

  • select *from 表1 inner join 表2 [ on 过滤条件] [ where 查询条件];

  • select *from 表1 cross join 表2 [ on 过滤条件] [ where 查询条件];

外连接

-左(外)连接:select * from 表1 left join 表2 on 连接条件[where 条件查询];

表1 查询结果是所有数据,表2 查询结果是与表1 重合部分的数据

面试题汇总

-右(外)连接

select * from 表1 right join 表2 on 连接条件[where 条件查询];

面试题汇总

MySQL 去重复(使用distinct 关键字)

根据全部字段的去重查询:select distinct * from table

根据某些字段的去重查询(不考虑查询其他字段):select distinct c_name,c_year,c_month from table

根据某些字段的去重查询(考虑查询其他字段):如果其他字段所有结果值都想保留,建议直接用group by 和group_concat 即可

select c_name,c_year,c_month,group_concat(‘,’) c_values from table

group by c_name,c_year,c_month

根据某些字段的去重查询,查询重复项以外的全部数据:一般去重是根据时间、ID 等,如时间最新/ID 最大/value 最大等等;

根据某些字段的去重查询,查询重复项(不包含原始项,只查询重复项)

根据某些字段,查询出所有重复的数据(包含原始项和重复项)

MySQL 事务隔离级别

  • Read Uncommitted (读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read )。

  • Read Committed (读取提交内容)

这是大多数数据库系统的默认隔离级别(但不是MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read ),因为同一事务的其他实例在该实例处理其间可能会有新的commit ,所以同一select 可能返回不同结果。

  • Repeatable Read (可重读)

这是MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read )。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的”幻影” 行。InnoDB 和Falcon 存储引擎通过多版本并发控制(MVCC ,Multiversion Concurrency Control )机制解决了该问题。

  • Serializable (可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

总结:

事务具有四个特征:

原子性( Atomicity )、一致性(Consistency )、隔离性(Isolation )和持续性(Durability )。

Mysql 的四种隔离级别

(1 )Read Uncommitted (读取未提交内容)

A 可以读取到B 还未提交的事务。

(2 )Read Committed (读取提交内容)

A 只能读取到B 已经提交的事务。并且A 事务中两次读到的内容不一致,原有就是B 提交事务。

(3 )Repeatable Read (可重读)

A 只能读取到B 已经提交的事务。并且A 事务中两次读到的内容一致,A 事务结束后再读取会读取到B 提交事务。

(4 )Serializable (可串行化)A 事务未提交,B 事务就等待。

脏读、幻读、不可重复读

脏读:读到了还未提交事务的数据。

幻读:两次事务读到的数据不一致。中间有新事务提交。

不可重复读:一次事务中不允许多次读,会发生数据不一致。中间有新事务提交。

SQL 执行计划

执行计划,简单的来说,是SQL 在数据库中执行时的表现情况,通常用于SQL 性能分析,优化等场景。在MySQL 使用explain 关键字来查看SQL 的执行计划。(这里我的理解是,存储引擎在执行sql 的时候,把一条sql 分解,列出来每一步需要干什么,并按照步骤依次执行,这样我们就能看出来哪个步骤耽误了时间,当然这也是接下来要讲的重点!)

查询SELECT * from employee WHERE name =’蒋峰1′;

img

EXPLAIN SELECT * from employee WHERE name =’蒋峰1′;

img

具体列的作用就说只了解过具体不是特别清楚

Redis

Redis 雪崩、穿透、击穿原理及其解决方案

缓存雪崩(大面积失效)

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

2.给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

缓存穿透(查询不存在)

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。

解决方案

1.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value 对写为key-null ,缓存有效时间可以设置短点,如30 秒。这样可以防止攻击用户反复用同一个id 暴力攻击

2.采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap 中,一个一定不存在的数据会被这个bitmap 拦截掉,从而避免了对底层存储系统的查询压力

缓存击穿(并发查一条)

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

1.设置热点数据永远不过期。

2.加互斥锁,互斥锁

Redis 的内存淘汰机制

Redis 清除过期key 的两种方式:定期删除和惰性删除

定期删除:

Redis 每隔100ms 随机抽取一些过期的key 进行检查,如果已经过期就对其进行删除

为什么是随机抽取?因为如果存储了大量数据,全部遍历一遍是非常影响性能的!

惰性删除:

每次获取key 时都会对key 进行判断是否过期,如果过期就将其删除

注意:Redis 中过期的key 并不会马上删除,因为定期删除可能正好没抽取到它,我们也没有访问它触发惰性删除

内存淘汰机制

如果定期删除漏掉了很多过期的key 未删除,而我们也没有去访问它,就会导致内存耗尽。

Redis 配置文件中可以设置maxmemory ,内存的最大使用量,到达限度时会执行内存淘汰机制

Redis 的内存淘汰策略是指在Redis 的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

全局的键空间选择性移除

  • noeviction :当内存不足以容纳新写入数据时,新写入操作会报错。

  • allkeys-lru :当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key 。(这个是最常用的)

  • allkeys-random :当内存不足以容纳新写入数据时,在键空间中,随机移除某个key 。

设置过期时间的键空间选择性移除

  • volatile-lru :当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key 。

  • volatile-random :当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key 。

  • volatile-ttl :当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key 优先移除。

总结

Redis 的内存淘汰策略的选取并不会影响过期的key 的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据;过期策略用于处理过期的缓存数据。

什么是Redis 的持久化?

Redis 持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

持久化的两种方式:

RDB 是Redis 默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb 。通过配置文件中的save 参数来定义快照的周期。

img

优点:

1 、只有一个文件dump.rdb ,方便持久化。

2 、容灾性好,一个文件可以保存到安全的磁盘。

3 、性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何IO 操作,保证了redis 的高性能

4.相对于数据集大时,比AOF 的启动效率更高。

缺点:

1 、数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间redis 发生故障,会发生数据丢失。

AOF 持久化(即Append Only File 持久化),则是将Redis 执行的每次写命令记录到单独的日志文件中,当重启Redis 会重新将持久化的日志中文件恢复数据。

当两种方式同时开启时,数据恢复Redis 会优先选择AOF 恢复。

img

优点:

1 、数据安全,aof 持久化可以配置appendfsync 属性,有always ,每进行一次 命令操作就记录到aof 文件中一次。

2 、通过append 模式写文件,即使中途服务器宕机,可以通过redis-check-aof 工具解决数据一致性问题。

缺点:

1 、AOF 文件比RDB 文件大,且恢复速度慢。

2 、数据集大的时候,比rdb 启动效率低。

优缺点是什么?

AOF 文件比RDB 更新频率高,优先使用AOF 还原数据。

AOF 比RDB 更安全也更大

RDB 性能比AOF 好

如果两个都配了优先加载AOF

Redis 提供两种持久化机制RDB (默认) 和AOF 机制:

-一般来说, 如果想达到足以媲美PostgreSQL 的数据安全性,你应该同时使用两种持久化功能。在这种情况下,当Redis 重启的时候会优先载入AOF 文件来恢复原始的数据,因为在通常情况下AOF 文件保存的数据集要比RDB 文件保存的数据集要完整。

-如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失,那么你可以只使用RDB 持久化。

-有很多用户都只使用AOF 持久化,但并不推荐这种方式,因为定时生成RDB 快照(snapshot )非常便于进行数据库备份, 并且RDB 恢复数据集的速度也要比AOF 恢复的速度要快,除此之外,使用RDB 还可以避免AOF 程序的bug 。

-如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。

Redis 模式优缺点

①主从复制

在主从复制中,数据库分为俩类,主数据库(master)和从数据库(slave)。其中主从复制有如下特点:

1.主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库

2.从数据库一般都是只读的,并且接收主数据库同步过来的数据

3.一个master 可以拥有多个slave ,但是一个slave 只能对应一个master

主从复制工作机制

当slave 启动后,主动向master 发送SYNC 命令。master 接收到SYNC 命令后在后台保存快照(RDB 持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和缓存的命令发送给slave 。slave 接收到快照文件和命令后加载快照文件和缓存的执行命令。

复制初始化后,master 每次接收到的写命令都会同步发送给slave ,保证主从数据一致性。

  • 1.优点:主从结构具有读写分离,提高效率、数据备份,提供多个副本等优点。

  • 2.不足:最大的不足就是主从模式不具备自动容错和恢复功能,主节点故障,集群则无法进行工作,可用性比较低,从节点升主节点需要人工手动干预。

②哨兵模式

哨兵的作用就是监视redis 系统的运行状况。功能如下:

-监控主从数据库是否正常运行

  • master 出现故障时,自动将slave 转化为master

-多哨兵配置的时候,哨兵之间也会自动监控

-多个哨兵可以监控同一个redis

当master 出现故障时,选取新的master ,选择规则如下:

①从在线的slave 中选择优先级最高的,优先级可以通过slave-priority 来设置

②如果有多个最高优先级的slave ,则选取复制最完整的当选

③如果以上条件都一样,选取id 最小的

哨兵模式下,如果原节点恢复之后,不会恢复成master 节点,而是恢复成slave

1.优点

哨兵模式是基于主从模式的,解决可主从模式中master 故障不可以自动切换故障的问题。

2.不足-问题

(1 )是一种中心化的集群实现方案:始终只有一个Redis 主机来接收和处理写请求,写操作受单机瓶颈影响。

(2 )集群里所有节点保存的都是全量数据,浪费内存空间,没有真正实现分布式存储。数据量过大时,主从同步严重影响master 的性能。

(3 )Redis 主机宕机后,哨兵模式正在投票选举的情况之外,因为投票选举结束之前,谁也不知道主机和从机是谁,此时Redis 也会开启保护机制,禁止写操作,直到选举出了新的Redis 主机。

③集群模式

将每个数据库节点的cluster-enable 配置打开即可。每个集群中至少需要三个主数据库才能正常运行。(三主三从)

集群模式就是分布式存储,即每台redis 存储不同的内容。

优点

1.采用去中心化思想,数据按照slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布;

2.可扩展性:可线性扩展到1000 多个节点,节点可动态添加或删除;

3.高可用性:部分节点不可用时,集群仍可用。通过增加Slave 做standby 数据副本,能够实现故障自动failover ,节点之间通过gossip 协议交换状态信息,用投票机制完成Slave 到Master 的角色提升;

4.降低运维成本,提高系统的扩展性和可用性。

缺点

  1. Redis Cluster 是无中心节点的集群架构,依靠Gossip 协议(谣言传播)协同自动化修复集群的状态。但Gossip 有消息延时和消息冗余的问题,在集群节点数量过多的时候,节点之间需要不断进行PING/PANG 通讯,不必须要的流量占用了大量的网络资源。虽然Reds4.0 对此进行了优化,但这个问题仍然存在。

2.数据迁移问题。Redis Cluster 可以进行节点的动态扩容缩容,这一过程,在目前实现中,还处于半自动状态,需要人工介入。在扩缩容的时候,需要进行数据迁移。而Redis 为了保证迁移的一致性,迁移所有操作都是同步操作,执行迁移时,两端的Redis 均会进入时长不等的阻塞状态,对于小Key ,该时间可以忽略不计,但如果一旦Key 的内存使用过大,严重的时候会接触发集群内的故障转移,造成不必要的切换。

总结

主从模式:master 节点挂掉后,需要手动指定新的master ,可用性不高,基本不用。

哨兵模式:master 节点挂掉后,哨兵进程会主动选举新的master ,可用性高,但是每个节点存储的数据是一样的,浪费内存空间。数据量不是很多,集群规模不是很大,需要自动容错容灾的时候使用。

集群模式:数据量比较大,QPS 要求较高的时候使用。Redis Cluster 是Redis 3.0 以后才正式推出,时间较晚,目前能证明在大规模生产环境下成功的案例还不是很多,需要时间检验。

怎么保证缓存数据和数据库数据的一致性问题

将不一致分为几种情况:

1.数据库有数据,缓存没有

2.数据库有数据,缓存也有数据,但数据不一致

3.缓存中有数据,数据库中没有

首先去缓存读取,如果读取不到再去数据库中读取,并将数据写入到缓存中。

需要更新数据库时先更新数据库,在删除对应的缓存信息

-系统引入缓存提高应用性能问题

-引入缓存后,需要考虑缓存和数据库双写一致性问题,可选的方案有:「更新数据库+更新缓存」、「更新数据库+删除缓存」

-不管哪种方案,只要第二步操作失败,都无法保证数据的一致性,针对这类问题,可以通过消息队列重试解决

-「更新数据库+更新缓存」方案,在「并发」场景下无法保证缓存和数据一致性,且存在「缓存资源浪费」和「机器性能浪费」的情况发生,一般不建议使用

-在「更新数据库+删除缓存」的方案中,「先删除缓存,再更新数据库」在「并发」场景下依旧有数据不一致问题,解决方案是「延迟双删」,但这个延迟时间很难评估,所以推荐用「先更新数据库,再删除缓存」的方案

-在「先更新数据库,再删除缓存」方案下,为了保证两步都成功执行,需配合「消息队列」或「订阅变更日志」的方案来做,本质是通过「重试」的方式保证数据一致性

-在「先更新数据库,再删除缓存」方案下,「读写分离+主从库延迟」也会导致缓存和数据库不一致,缓解此问题的方案是「强制读主库」或者「延迟双删」,凭借经验发送「延迟消息」到队列中,延迟删除缓存,同时也要控制主从库延迟,尽可能降低不一致发生的概率。

Java

Java 堆与栈区别

(1 )管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;

(2 )空间大小不同。每个进程拥有的栈大小要远远小于堆大小。理论上,进程可申请的堆大小为虚拟内存大小,进程栈的大小64bits 的Windows 默认1MB ,64bits 的Linux 默认10MB ;

(3 )生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。

(4 )分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2 种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca()函数分配,但是栈的动态分配和堆是不同的,它的动态分配是由操作系统进行释放,无需我们手工实现。

(5 )分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。

(6 )存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP ),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP ),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS 段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

Java 单元测试目的

单元测试的作用:单元测试是编写测试代码,用以检测特定的、明确的、细颗粒的功能!严格来说,单元测试只针对功能点进行测试,不包括对业务流程正确性的测试。单元测试不仅仅是要保证代码的正确性,一份好的单元测试报告,还要完整地记录问题的所在和缺陷以及正确的状态,方便后面代码的修复,重构和改进!

单元测试主要包括以下几个方面

接口功能性测试:接口功能的正确性,即保证接口能够被正常调用,并输出有效数据!

局部数据结构测试:保证数据结构的正确性

边界条件测试:测试

所有独立代码测试:保证每一句代码,所有分支都测试完成,主要包括代码覆盖率,异常处理通路测试

异常模块测试,后续处理模块测试:是否包闭当前异常或者对异常形成消化,是否影响结果!

Java 全局异常处理

①基于@ControllerAdvice 注解的Controller 层的全局异常统一处理

②基于Springboot 自身的全局异常统一处理,主要是实现ErrorController 接口或者继承AbstractErrorController 抽象类或者继承BasicErrorController 类

Java 异常类

面试题汇总

Java 截取字符串的方法

①通过subString(起始位置,结束位置)方法来进行字符串截取,返回字符串中的子字符串

通过StringUtils 提供的方法:subString()方法

split()+正则表达式来进行截取:将字符串按照分割符截取,以数组形式返回

Java String 转换Int

–使用Integer.parseInt(string )方法

–使用Integer.valueof (string )方法

–使用Apache Commons 的NumberUtils.toInt (string )方法

–使用Apache Commons 的NumberUtils.createInteger (string )方法

–使用Guava 库的Ints.tryParse (string )方法

–使用Integer.decode (string )方法

–使用新的整数(字符串):Integer integerEmpId10 = new Integer(字符串)

JDK1.8 新特性

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

  • 方法引用-方法引用提供了非常有用的语法,可以直接引用已有的Java 类或对象(实例)的方法或构造器。

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

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

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

  • 并行流和串行流-在jdk1.8 新的stream 包中针对集合的操作也提供了并行操作流和串行操作流

  • 日期时间API-加强对日期与时间的处理。

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

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

Aop 和IOC 的理解

IOC:控制反转,也叫依赖注入,它并不是一种技术实现,而是一种设计思想。

在实际项目开发中,我们往往是通过类与类之间的相互协作来完成特定的业务逻辑,这个时候,每个类都要管理与自己有交互的类的引用和依赖,这就使得代码的维护异常困难并且耦合度过高,而IOC 的出现正是为了解决这个问题,IOC 将类与类的依赖关系写在配置文件中,程序在运行时根据配置文件动态加载依赖的类,降低的类与类之间的耦合度。

许多应用都是通过彼此间的相互合作来实现业务逻辑的,如类A 要调用类B 的方法,以前我们都是在类A 中,通过自身new 一个类B ,然后在调用类B 的方法,现在我们把new 类B 的事情交给spring 来做,在我们调用的时候,容器会为我们实例化。我们实例化一个对象时,都是使用类的构造方法来new 一个对象,这个过程是由我们自己来控制的,而控制反转就把new 对象的工交给了Spring 容器。

AOP :即面向切面编程,也就是纵向的编程

通过AOP ,确认每一个操作数据库方法为一个连接点,这些连接点组成了一个切面。当程序运行到其中某个一个切点时,我们将事务管理模块顺势织入对象中,通过通知功能,完成整个事务管控的实现。这样一来,所有的操作数据库的方法中不需要再单独关心事务管理的内容,只需要关注自身的业务代码的实现即可。所有的事务管控相关的内容都通过AOP 的方式进行了实现。简化了代码的内容,将目标对象复杂的内容进行解耦,分离业务逻辑与横切关注点,aop 实际上就是在不改变代码的前提下来实现对代码的增强。

比如业务A 和业务B 现在需要一个相同的操作,传统方法我们可能需要在A 、B 中都加入相关操作代码,而应用AOP 就可以只写一遍代码,A 、B 共用这段代码。并且,当A 、B 需要增加新的操作时,可以在不改动原代码的情况下,灵活添加新的业务逻辑实现。

使用@Aspect 注解将一个java 类定义为切面

使用@Pointcut 定义一个切入点,可以是一个规则表达式,比如下例中某个package 下的所有函数,也可以是一个注解等

使用@Before 在切入点开始处切入内

使用@After 在切入点结尾处切入内

使用@AfterReturning 在切入点return 内容之后切入内容(可以用来对处理返回值做一些加工处理

使用@Around 在切入点前后切入内容,并自己控制何时执行切入点自身的内

使用@AfterThrowing 用来处理当切入内容部分抛出异常之后的处理逻辑

IOC 注入方式

1.setter

原理 :在目标对象中,定义需要注入的依赖对象对应的属性和setter 方法;”让ioc 容器调用该setter 方法”,将ioc 容器实例化的依赖对象通过setter 注入给目标对象,封装在目标对象的属性中。

2.构造器

原理 :为目标对象提供一个构造方法,在构造方法中添加一个依赖对象对应的参数。ioc 容器解析时,实例化目标对象时会自动调用构造方法,ioc 只需要为构造器中的参数进行赋值;将ioc 实例化的依赖对象作为构造器的参数传入。

3.接口注入

原理 :为依赖对象提供一个接口实现,将接口注入给目标对象,实现将接口的实现类注入的效果。比如HttpServletRequest HttpServletResponse 接口

注意:这是ioc 提供的方式,spring 中的ioc 技术并没有实现该种注入方式】

4.方法注入

Spring

SpringBoot 核心实现原理

@SpringBootApplication 被@Configuration 、@EnableAutoConfiguration 、@ComponentScan 注解所修饰,也就是说,Springboot 对这三个注解进行了封装整合

①@Configuration :表示该类是spring boot 的配置类

②@EnableAutoConfiguration :程序启动时,自动加载spring boot 默认的配置

③@ComponentScan :程序启动时,它会去扫描当前包及子包下所有的类

SringBoot 的自动装配原理

spring 中核心注解@SpringBootApplication 注解,它又由三个注解组成:@SpringBootConfiguration (组合了@Configuration 注解) ,@EnableAutoConfiguration ,@ComponentScan

首先使用@EnableAutoConfiguration 开启自动装配,关键是selectImports 方法,逻辑大致是:

①从配置文件Meta-Inf / spring.factories 加载所有可能用到的自动配置类

②去重,将exclude 和excludeName 属性携带的类排除

③过滤,满足条件的自动装配类返回

SpringMVC 和SpringBoot 的区别

1.springmvc 是基于Servlet 的一个MVC 框架,主要解决WEB 开发的问题

2.springboot 是基于spring4.0 整合出来的一套快速开发的整合包,引入了自动装配的原理,它的开发模式是约定大于配置,不需要写很多的配置文件,也不需要安装Tomcat 这类容器服务器,减少了项目搭建的复杂度

SpringMVC 的执行流程

1 、 用户发送请求至前端控制器DispatcherServlet 。

2 、DispatcherServlet 收到请求调用HandlerMapping 处理器映射器。

3 、 处理器映射器找到具体的处理器(可以根据xml 配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet 。

4 、DispatcherServlet 调用HandlerAdapter 处理器适配器。

5 、HandlerAdapter 经过适配调用具体的处理器(Controller ,也叫后端控制器)。

6 、Controller 执行完成返回ModelAndView 。

7 、HandlerAdapter 将controller 执行结果ModelAndView 返回给DispatcherServlet 。

8 、DispatcherServlet 将ModelAndView 传给ViewReslover 视图解析器。

9 、ViewReslover 解析后返回具体View 。

10 、DispatcherServlet 根据View 进行渲染视图(即将模型数据填充至视图中)。

11 、DispatcherServlet 响应用户。

面试题汇总

Springbean 的生命周期和作用域

四个阶段:

  1. Bean 的实例化阶段

  2. Bean 的设置属性阶段

  3. Bean 的 初始化阶段

  4. Bean 的销毁阶段

详细步骤:

1 、 启动spring 容器,也就是创建beanFactory(bean 工厂),

一般用的是beanFactory 的子类applicationcontext,

applicationcontext 比一般的beanFactory 要多很多功能,比如aop 、事件等。

通过applicationcontext 加载配置文件,或者利用注解的方式扫描将bean

的配置信息加载到spring 容器里面。

2 、 加载之后,spring 容器会将这些配置信息(java bean 的信息),封装成BeanDefinition 对象

BeanDefinition 对象其实就是普通java 对象之上再封装一层,

赋予一些spring 框架需要用到的属性,比如是否单例,是否懒加载等等。

3 、 然后将这些BeanDefinition 对象以key 为beanName,

值为BeanDefinition 对象的形式存入到一个map 里面,

将这个map 传入到spring beanfactory 去进行springBean 的实例化。

4 、 传入到pring beanfactory 之后,利用BeanFactoryPostProcessor 接口这个扩展点

去对BeanDefinition 对象进行一些属性修改。

5 、 开始循环BeanDefinition 对象进行springBean 的实例化,springBean 的实例化也就

是执行bean 的构造方法(单例的Bean 放入单例池中,但是此刻还未初始化),

在执行实例化的前后,可以通过InstantiationAwareBeanPostProcessor 扩展点

(作用于所有bean)进行一些修改。

6 、spring bean 实例化之后,就开始注入属性,

首先注入自定义的属性,比如标注@autowrite 的这些属性,

再调用各种Aware 接口扩展方法,注入属性(spring 特有的属性),

比如BeanNameAware.setBeanName,设置Bean 的ID 或者Name;

7 、 初始化bean,对各项属性赋初始化值,,初始化前后执行BeanPostProcessor

(作用于所有bean)扩展点方法,对bean 进行修改。

8 、 此时已完成bean 的初始化,在程序中就可以通过spring 容器拿到这些初始化好的bean 。

9 、 随着容器销毁,springbean 也会销毁,销毁前后也有一系列的扩展点。

销毁bean 之前,执行@PreDestroy 的方法

销毁时,执行配置文件或注解中指定的destroy-method 方法。

五个作用域

(1 )singleton :单例模式,Spring IoC 容器中只会存在一个共享的Bean 实例,无论有多少个Bean 引用它,始终指向同一对象Singleton 作用域是Spring 中的缺省作用域,也可以显示的将Bean 定义为singleton 模式,配置为:

(2 )prototype:原型模式,每次通过Spring 容器获取prototype 定义的bean 时,容器都将创建一个新的Bean 实例,每个Bean 实例都有自己的属性和状态,而singleton 全局只有一个对象。根据经验,对有状态的bean 使用prototype 作用域,而对无状态的bean 使用singleton 作用域。

(3 )request :在一次Http 请求中,容器会返回该Bean 的同一实例。而对不同的Http 请求则会产生新的Bean ,而且该bean 仅在当前Http Request 内有效。

针对每一次Http 请求,Spring 容器根据该bean 的定义创建一个全新的实例,且该实例仅在当前Http 请求内有效,而其它请求无法看到当前请求中状态的变化,当当前Http 请求结束,该bean 实例也将会被销毁。

(4 )session :在一次Http Session 中,容器会返回该Bean 的同一实例。而对不同的Session 请求则会创建新的实例,该bean 实例仅在当前Session 内有效。

同Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的session 请求内有效,请求结束,则实例将被销毁。

(5 )global Session :在一个全局的Http Session 中,容器会返回该Bean 的同一个实例,仅在使用portlet context 时有效。

集合

ArrayXxx:底层数据结构是数组,查询快,增删慢

LinkedXxx:底层数据结构是链表,查询慢,增删快

HashXxx:底层数据结构是哈希表。依赖两个方法:hashCode()和equals()

Arrylist 与Linklist 的区别

①ArrayList 是实现了基于动态数组的数据结构,LinkedList 是基于链表结构。

②对于随机访问的get 和set 方法,ArrayList 要优于LinkedList ,因为LinkedList 要移动指针。

③对于新增和删除操作add 和remove ,LinkedList 比较占优势,因为ArrayList 要移动数据。

④对ArrayList 和LinkedList 而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList 而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList 而言,这个开销是 统一的,分配一个内部Entry 对象。

⑥LinkedList 集合不支持 高效的随机随机访问(RandomAccess ),因为可能产生二次项的行为。

ArrayList 的空间浪费主要体现在在list 列表的结尾预留一定的容量空间,而LinkedList 的空间花费则体现在它的每一个元素都需要消耗相当的空间

Arraylist 和Hashmap 区别

相同点:

-都是线程不安全,不同步

-都可以储存null 值

-获取元素个数方法一样,都用size()方法获取

区别:

-实现的接口

ArrayList 实现了List 接口(Collection (接口)->List (接口)->ArrayList (类)),底层使用的是数组;而HashMap 现了Map 接口(Map (接口)->HashMap (类)),底层使用的是Hash 算法存储数据。

-存储元素

ArrayList 以数组的方式存储数据,里面的元素是有顺序,可以重复的;而HashMap 将数据以键值对的方式存储,键的哈希码(hashCode )不可以相同,相同后面的值会将前面的值覆盖,值可以重复,里面的元素无序。

-添加元素的方法

ArrayList 用add(Object object)方法添加元素,而HashMap 用put(Object key, Object value)添加元素。

-默认的大小和扩容

在 Java 7 中,ArrayList 的默认大小是10 个元素,HashMap 的默认大小是16 个元素(必须是2 的幂)。

Hashmap 遍历方式

-使用For-Each 迭代entries

-使用For-Each 迭代keys 和values

-使用Iterator 迭代

-迭代keys 并搜索values (低效的)

线程安全的集合

Vector

Vector 和ArrayList 类似,是长度可变的数组,与ArrayList 不同的是,Vector 是线程安全的,它给几乎所有的public 方法都加上了synchronized 关键字。由于加锁导致性能降低,在不需要并发访问同一对象时,这种强制性的同步机制就显得多余,所以现在Vector 已被弃用

HashTable

HashTable 和HashMap 类似,不同点是HashTable 是线程安全的,它给几乎所有public 方法都加上了synchronized 关键字,还有一个不同点是HashTable 的K ,V 都不能是null ,但HashMap 可以,它现在也因为性能原因被弃用了

ConcurrentHashMap

ConcurrentHashMap 和HashTable 都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable 的加锁方法是给每个方法加上synchronized 关键字,这样锁住的是整个Table 对象。而ConcurrentHashMap 是更细粒度的加锁.

CopyOnWriteArraySet

它们是加了写锁的ArrayList 和ArraySet ,锁住的是整个对象,但读操作可以并发执行

线程

创建线程的方式

  • 继承Thread 类创建线程类

(1 )定义Thread 类的子类,并重写该类的run 方法,该run 方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

(2 )创建Thread 子类的实例,即创建了线程对象。

(3 )调用线程对象的start()方法来启动该线程。

  • 通过Runnable 接口创建线程类

(1 )定义runnable 接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

(2 )创建Runnable 实现类的实例,并依此实例作为Thread 的target 来创建Thread 对象,该Thread 对象才是真正的线程对象。

(3 )调用线程对象的start()方法来启动该线程。

  • 通过Callable 和Future 创建线程

(1 )创建Callable 接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2 )创建Callable 实现类的实例,使用FutureTask 类来包装Callable 对象,该FutureTask 对象封装了该Callable 对象的call()方法的返回值。

(3 )使用FutureTask 对象作为Thread 对象的target 创建并启动新线程。

(4 )调用FutureTask 对象的get()方法来获得子线程执行结束后的返回值

采用继承Thread 类方式:

(1 )优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this ,即可获得当前线程。

(2 )缺点:因为线程类已经继承了Thread 类,所以不能再继承其他的父类。

采用实现Runnable 接口方式:

(1 )优点:线程类只是实现了Runable 接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU 代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

(2 )缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

什么是线程池?有哪几种创建方式?

线程池顾名思义就是事先创建好多个线程对象放入线程池中,当需要线程对象时就去容器里面进行拿去,使用完毕之后不需要销毁而是重新放入线程池中,以减少创建和销毁线程对象的开销。

创建方式:Executor 类创建四种常见的线程池

1.newSingleThreadExecutor :创建一个单线程化的线程池,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行

2.newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

3.newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

4.newScheduledThreadPool :创建一个定长线程池,支持定时及周期性任务执行

synchronized 和Lock 的区别是什么

synchronized 实现原理

Java 中每一个对象都可以作为锁,这是synchronized 实现同步的基础:

1.普通同步方法,锁是当前实例对象

2.静态同步方法,锁是当前类的class 对象

3.同步方法块,锁是括号里面的对象

img

区别

1.来源:

lock 是一个接口,而synchronized 是java 的一个关键字,synchronized 是内置的语言实现;

2.异常是否释放锁:

synchronized 在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock 发生异常时候,不会主动释放占有的锁,必须手动unlock 来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch 包起来,finally 中写入unlock ,避免死锁的发生。)

3.是否响应中断

lock 等待锁过程中可以用interrupt 来中断等待,而synchronized 只能等待锁的释放,不能响应中断;

4.是否知道获取锁

Lock 可以通过trylock 来知道有没有获取锁,而synchronized 不能;

  1. Lock 可以提高多个线程进行读操作的效率。(可以通过readwritelock 实现读写分离)

6.在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock 的性能要远远优于synchronized 。所以说,在具体使用时要根据适当情况选择。

  1. synchronized 使用Object 对象本身的wait 、notify 、notifyAll 调度机制,而Lock 可以使用Condition 进行线程之间的调度

其他

动态代理的两种方式

JDK 动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler 来处理。

CGlib 动态代理:利用ASM (开源的Java 字节码编辑库,操作字节码)开源包,将代理对象类的class 文件加载进来,通过修改其字节码生成子类来处理。

区别:JDK 代理只能对实现接口的类生成代理;CGlib 是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final 修饰的类。

Jwt token 区别

Token 需要查库验证token 是否有效,而JWT 不用查库,直接在服务端进行校验,因为用户的信息及加密信息,和过期时间,都在JWT 里,只要在服务端进行校验就行,并且校验也是JWT 自己实现的。

Token 基本原理

Token(就是加密的字符串,使用MD5,等不可逆加密算法,一定要保证唯一性)

1.客户端使用用户名跟密码请求登录

2.服务端收到请求,去验证用户名与密码

3.验证成功,服务端会签发一个Token 保存到(Session,redis,mysql …)中,然后再把这个Token 发送给客户端

4.客户端收到Token 以后可以把它存储起来,比如放在Cookie 里或者Local Storage 里

5.客户端每次向服务端请求资源的时候需要带着服务端签发的Token

6.服务端收到请求,验证密客户端请求里面带着的Token 和服务器中保存的Token 进行对比效验,如果验证成功,就向客户端返回请求的数据

JWT 基本原理

JWT 是json web token 缩写。它将用户信息加密到token 里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证JWTToken 的正确性,只要正确即通过验证。

JWT 包含三个部分:Header 头部,Payload 负载和Signature 签名。由三部分生成JwtToken ,三部分之间用”.”号做分割。 校验也是JWT 内部自己实现的,并且可以将你存储时候的信息从JwtToken 中取出来无须查库

1.客户端使用用户名跟密码请求登录

2.服务端收到请求,去验证用户名与密码

3.验证成功,服务端会签发一个JwtToken,无须存储到服务器,直接再把这个JwtToken 发送给客户端

4.客户端收到JwtToken 以后可以把它存储起来,比如放在Cookie 里或者Local Storage 里

5.客户端每次向服务端请求资源的时候需要带着服务端签发的JwtToken

6.服务端收到请求,验证密客户端请求里面带着的JwtToken,如果验证成功,就向客户端返回请求的数据

Shiro+jwt

①前端请求登录,传入用户名userName 和密码password 和令牌token ;

②后端进入jwtFilter 拦截,判断当前请求中是否是登录请求(可以通过是否存在token,也可以通过判断请求的地址是否是登录地址)

③如果是登录请求,就不执行shiro 认证和授权,直接进入控制器进行帐号和密码校验,校验成功生成token 返回;

④如果是非登录请求,jwtFilter 执行executeLogin 方法进入自定义realm 进行认证doGetAuthenticationInfo (认证不通过,抛出异常,异常的处理稍后详细说明)和授权doGetAuthorizationInfo ,都通过然后进入自己的控制器;

token 包含三部分:

  1. Header 头部(用户名)

  2. Payload 负载 (时间戳)

  3. Signature 签名(密码)

shiro 的优点

1.简单的身份验证,支持多种数据

2.对角色的简单授权

3.支持一级缓存,提升应用程序的性能

4.不跟其他框架绑定,可以独立运行

JWT 实现认证流程

1.用户登陆之后,使用密码对账号进行签名生成并返回token 并设置过期时间;

2.将token 保存到本地,并且每次发送请求时都在header 上携带token 。

  1. shiro 过滤器拦截到请求并获取header 中的token ,并提交到自定义realm 的doGetAuthenticationInfo 方法。

4.通过jwt 解码获取token 中的用户名,从数据库中查询到密码之后根据密码生成jwt 效验器并对token 进行验证。

通俗点来说,就是服务器根据给定的密钥和算法,对用户名或者ID 之类(只要是能判断是某一个用户的唯一标识都行)加上过期时间的时间戳进行加密,然后生成类似XXX.XXX.XXX 的字符串,这个字符串就是所谓的token ,以后要想访问服务器得到资源,只需要在请求头带上token ,服务器拿到这个token ,再进行解密验证操作,判断该用户是否是有效用户,然后放行。

RabbitMQ 和Kafka 区别

在实际生产应用中,通常会使用kafka 作为消息传输的数据管道,rabbitmq 作为交易数据作为数据传输管道,主要的取舍因素则是是否存在丢数据的可能;rabbitmq 在金融场景中经常使用,具有较高的严谨性,数据丢失的可能性更小,同事具备更高的实时性;而kafka 优势主要体现在吞吐量上,虽然可以通过策略实现数据不丢失,但从严谨性角度来讲,大不如rabbitmq ;而且由于kafka 保证每条消息最少送达一次,有较小的概率会出现数据重复发送的情况;

Original: https://www.cnblogs.com/antlerJiudao/p/16582034.html
Author: Antler_九道街
Title: 面试题汇总

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/644259/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球