JAVASE
JDK,JRE,JVM
java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具
Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库
Java Virtual Machine是Java虚拟机
switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),
但是长整型(long)在目前所有的版本中都是不可以的。
面向对象和面向过程的区别
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
面向对象:
优点:易维护、易复用、易扩展
为什么重写equals还要重写hashcode?
在Object类中,equals方法比较的是hashcode码,而hashcode码为默认的内存地址(Object的hashcode方法是本地方法,也就是用c语言或c++实现的)
而在某些类中,比如String,HashMap类中,equals方法比较的数值是否相同或者key值是否相同,地址就会不一样
而有个规定,就是两个对象equals相同,则该两个对象hashcode也相同。
Hashcode的作用?
hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同
“==”比较的是什么
对于对象 比较的就是两对象的引用类型
对于基本数据类型,比较数值是否相等
一个十进制的数在内存中是怎么存的?
补码
~数字 代表其数字反码的补码的反码减一
数据类型有哪些
基本数据类型:***
引用数据类型:数组,类,接口
不会初始化子类的几种方法
-
调用的是父类的static方法或者字段
-
调用的是父类的final方法或者字段
-
通过数组来引用
为什么要有包装类
基本类型也具有对象的特征
它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作
区别:声明方式不同(后者需要new),存储方式及位置不同(前栈后堆),初始值不同(后者null),使用方式不同(集合容器中用包装类)
说一说String,StringBuffer,StringBuilder的区别
String对象在创建后不是不可变的,后两者就可以是可以修改的
StringBuffer是线程安全的,而StringBuilder则没有实现线程安全功能,所以性能略高
StringBuffer类中的方法都添加了synchronized关键字,也就是给这个方法添加了一个锁,用来保证线程安全
为什么String对象是不可变的呢
string方法使用了final进行修饰
字符串池如果没有使字符串不可变,那就不可能,你定义两个数字他的值是相同的,如果可变,那么修改其中一个值的时候,字符串池中的值发生变化,那其他指向他的值也会变化,就会带来很多风险
字符串已被广泛用作许多 Java 类的参数,比如说hashMap的键
由于 String 是不可变的,它可以安全地共享许多线程,这对于多线程编程非常重要. 并且避免了 Java 中的同步问题,不变性也使得String 实例在 Java 中是线程安全的,这意味着你不需要从外部同步 String 操作
String 在 Java 中是不可变的另一个原因是允许 String 缓存其哈希码,使得它在 Java 中的 HashMap 中使用的 HashMap 键非常快
抽象类与接口的区别
Java 7以及以前的版本,那么接口中可以包含的内容有:1. 常量;2. 抽象方法
如果是Java 8,还可以额外包含有:3. 默认方法;4. 静态方法
如果是Java 9,还可以额外包含有:5. 私有方法
抽象类由普通类与抽象方法构成,可以使用任意权限,使用extends关键字,且一个子类只能继承一个抽象类,一个抽象类能实现若干个接口
接口由全局常量与抽象方法构成,只能使用public修饰,使用implement关键字,一个类可以实现多个接口,接口不等继承抽象类,但是可以继承多个父接口
面向对象的基本原则
单一性原则、开闭原则、里氏替换原则、依赖倒转原则、接口隔离原则、迪米特法则、合成聚合复用
说一说你对内部类的理解以及其中匿名内部类的特点
可以将一个类的定义放在另外一个类的定义内部,这就是内部类
分为成员内部类、局部内部类、匿名内部类和静态内部类
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
- 内部类不为同一包的其他类所见,具有很好的封装性;
- 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
- 匿名内部类可以很方便的定义回调
IO
Java 中 IO 流分为⼏种?
从流向分为输入流与输出流
从流操作的单元分为字节流与字符流
从流的角色划分为节点流和处理流
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
BIO,NIO,AIO 有什么区别?
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
多用于单线程,会一直监听一个ServerSocket 等待read方法调用,但是无法很好的解决C10k 以及C10M问题
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
在jdk1.4版本引入,提供了Channel,Selector,Buffer等抽象
在一些简单的nio使用时,先创建Socket处理通道,然后将其设置为非阻塞通道
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
反射
什么是反射
在一个类中会定义一些不希望被外界访问到的属性和方法,所有用正常方法难以访问它的属性与方法,但是可以使用反射机制来获取
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制
什么是 java 序列化?什么情况下需要序列化?
序列化就是一种用来处理对象流的机制,序列华江对象转化为容易传输的格式的过程
当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理
获取反射的方法你知道哪些
1.通过new对象实现反射机制
2.通过路径实现反射机制
3.通过类名实现反射机制
通过getDeclaredConstructors可以返回类的所有构造方法
通过getConstructors方法获取类中 所有的public类型的构造方法
getParameterTypes可以得到构造方法的所有参数
得到类的实例,我们主要借助于newInstance方法
Constructor Method Field AccessibleObject (左三父类)
setAccessible 爆破
反射的优缺点
优点:能动态的获取类的实例,提高系统的灵活与扩展,实现无比强大的功能
缺点:性能较低,相对不安全,破坏了类的封装性
Private修饰方法可以通过反射访问,那Private的意义在哪
Private修饰符不是为了绝对安全设计的,是对用户常规使用的约束
当然Private可以清晰看出来清晰的类结构
容器
说一下你对map的理解
java中定义map为一个接口,其主要的实现类有四个,HashMap,Hashtable,LinkedHashMap,TreeMap
map主要是用于存储键值对
使用最多的还是HashMap,HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null,HashMap也不支持线程的同步
Hashtable与HashMap相似,但是他支持线程同步;
LinkedHashMap是HashMap的一个子类,它保存了记录的插入顺序,使用迭代器遍历时,得到的记录肯定是先插入的
TreeMap实现了SortMap接口,所以它能保存它的键值并按升序排序,遍历的时候,记录也是排序的
hash冲突的解决办法
- 再hash法
- 开放地址法
- 链地址法
- 建立公共溢出空间
容器Collection 和 Collections 有什么区别?
Collection是JDK中集合层次结构中的最根本的接口
Collections是一个包装类。它包含有各种有关集合操作的静态多态方法,不能实例化,像一个Collection集合框架中的工具类
如何实现数组和 List 之间的转换?
- for循环
- 使用了asList()方法
- 使用Collections.addAll()
jdk1.7中hashmap的实现方式
数组+链表
hashmap创建初始化的容量设置定义方式
hashmap创建的时候可以设定容量与负载因子,默认大小一般为16,0.75
初始化大小为 大于k的 2的整数次方
在代码实现上是使用异或和位运算的的出需要扩容大小的一半
hashmap中1.7版本put方法的实现方式
判断数组为空,如果是空的就要进行初始化
不为空,计算 k 的 hash 值,通过长度 & hash计算应当存放在数组中的下标 index;
查看是否有该key的值,存在就覆盖
不存在 ,存储在数组下标的链表链中(头插法)
在链表插入的时候,先插入在链表头结点(及堆中链表的头结点),再将数组位置引用替换为刚插入的值
插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍
扩容过程中需要用一个链表来重新计算各个点对于不同长度数组所应该存储的位置
每一次的操作也会增加modCount 属于是fast-fail机制 为了处理多线程同时处理该map的一种异常机制
concurrentHashMap与HashTable
HashTable是在方法前添加了synchronized 给对象加锁实现了线程安全
ArrayBlockingQueue 原理
阻塞队列是在队列的基础上增加了两个附加操作,
- 在队列为空的时候获取元素的线程会等待队列变为非空,
- 当队列满了的时候,存储元素的线程会等待队列为可用
由于阻塞对内的这样一个特性,可以非常容易的去实现生产者和消费者这样一个模型,说生产者需要关心数据的一个生产,而消费者只需要关心数据的一个消费,如果队列满了生产者就等待,同样队列空了消费者也需要等待
实现这样一个阻塞队列,需要用到两个非常关键的技术
队列元素的一个存储
线程的阻塞和唤醒而ArrayBlockingQueue,它是基于数组结构的组成队列,也就是说队列元素是存储在数组结构里面,并且由于数组的长度有限制的,为了达到循环生产和循环消费,ArrayBlockingQueue里用到了一个循环数组,而线程的阻塞和唤醒,线程的阻塞和唤醒用到了JUC包里面的一个ReentrantLock和condition,condition的相当于wait/notify在JUC里面的一个实现
MYSQL
char 和 varchar 的区别是什么?
1、char的长度是不可变的,而varchar的长度是可变的
2、超出长度自动截取
3、char(10)和char(10),都表示可存10个字符,无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放10个
4、char最多可以存放255个字符, varchar的最大长度为65535个字节,varchar可存放的字符数跟编码有关
5、char和varchar的最大长度限制是mysql规定的
数据库的三范式是什么?
第一范式 每个列都不可以拆分
第二范式 在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
MYSQL的体系结构图
MYSQL的客服端
MYSQL的服务端
- MYSQL连接层
- MYSQL 服务层
- MYSQL 接口
- MYSQL Parser解析器
- MYSQL Optimizer优化器
- MYSQL 缓存
- MYSQL存储引擎层
- MYSQL数据存储层 文件系统等
InnoDB架构
内存结构
Buffer Pool 缓冲池 free page/clean page /dirty page
Change Pool 更改缓冲池
自适应哈希
Log Buffer 日志缓冲区
InnoDB事务的实现原理
原子性:undo log:回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)
undo log是逻辑日志,事务插入,日志则删除反向操作
一致性
持久性:redo log 用于刷新脏页到磁盘时,发生错误,进行数据恢复
为什么不直接从缓冲区直接写到磁盘而是用redo log来进行磁盘操作?
直接缓存是随机操作,redo log是顺序操作 性能更高(redo log的两个空间也会时间性删除更新)
MVCC实现原理
1.当前读:当开启事务时,InnoDB事务隔离级别为可重复读,但是使用行共享锁或者排它锁都是在你事务还没结束时读取别人提交的事务
2.快照读 读取的记录可能是历史数据,不加锁,是非阻塞读
3.MVCC 多版本控制
- 记录隐藏字段
- 最近修改事务ID、回滚指针、隐藏主键
- undo Log日志
- undo log版本链,记录之前的修改记录以及更早的记录
- readView
- 通过事务id与版本链事务id比较,查看事务完成度以及操作是否允许
RC隔离级别下,在事务中每一次执行快照度时生成ReadView
RR隔离级别下,仅在十五中第一次执行快照读时生成ReadView 后续复用该ReadView
数据库引擎MYISAM与INNODB的区别
Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。增删改InnoDB更优,支持哈希索引不支持全文索引
MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。Select MyISAM更优,支持全文索引不支持哈希索引
MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。
为什么InnoDB表必须建主键,并且他推荐使用整形的自增主键?
如果你不建主键,那么数据库会自动选择一个没有重复值的列,或者用一个你看不到的列作为索引进行存储值,那就建议创建表的时候也建主键,主键可以当做索引使用
b+树中对数据存储为了方便范围查找会对进行排序,整形自增更易于存储
什么是索引
索引是帮助MYSQL高效获取数据排好序的数据结构
优点:它可以加快数据的检索速度,提高数据检索效率,降低数据库IO的成本
缺点:但是创建维护(增删改更新表的操作)索引需要时间,而且需要占物理空间
索引有哪些结构
BTREE索引
Hash索引:只有Memory引擎支持
R-TREE索引(空间索引):是MYISAM引擎的特殊索引类型
Full-text索引(全文索引):是MYISAM引擎的特殊索引类型,主要用于全文索引,InnoDB从版本5.6开始也支持
BTREE 又称为多路平衡搜索树
B-TREE
- 树中每个节点最多包含m个孩子(m叉BTREE情况下)
- 除根节点与叶子节点外,,每个节点至少有[ceil(m/2)]个孩子
- 若根节点不是叶子节点,那就至少有两个孩子
- 所有叶子节点都在同一层
- 每个非叶子节点由n个key与n+1个指针组成
B+TREE(B树的变种)
- n叉B+TREE最多含有n个key,而BTREE最多含有n-1个key
- B+TREE的叶子节点保存所有key信息,依key大小顺序排列
- 所有的非叶子节点都可以看做是key的索引部分
mysql中的B+Tree进行了优化,在叶子节点上添加了前后指针
B-Tree,B+Tree,Hash索引差别
hash 结构为链表加数组,不支持范围查找
B+Tree
非叶子节点不存储data,只存储索引冗余,可以放更多的索引
叶子节点用指针连接,提高区间访问的性能
B-Tree
叶子节点的指针为空
索引元素不重复
数据索引递增排序
索引有哪些类型
单值索引:一个索引只包含单个列,一个表可以有多个单列索引
唯一索引:索引列的值必须唯一,允许有空值(可以多个null)
复合索引:即一个索引包含多个列
设定主键的时候Mysql会默认创建一个主键索引
索引的语法
创建索引
CREATE [UNIQUE|FULLTEXT|SPATIAL] ##索引类型
INDEX index_name
[USING index_type] ##索引结构
on table_name(index_col_name,..)
查看索引
show index from table_name
删除索引
DROP INDEX index_name ON table_name
ALTER索引
alter table table_name add [primary|unique|index|fulltext]##主键/唯一/普通/全文
index_name(colum_list);
索引的设计原则
- 对查询频率高,数据量大的表建立索引
- 索引字段选择上,最佳是选择经常在where子句中挑取最常用
- 多用唯一索引,区分越高效率越高
- 索引不是越多越好,太多,需要选择,降低了效率还有相当高的维护代价
- 多使用短索引,这样在指定的内存上可以储存更多的索引值
- 利用最左前缀原则,主要是在复合索引中
什么是视图?视图的作用?
视图是一种虚拟存在的表,数据并不在数据库中实际存在
视图就是一条SELECT语句执行后返回的结果集
- 简单 不需要关心表结构,关联条件和筛选条件
- 安全 使用视图的用户只能访问他们被允许查询的结果集
- 数据独立 一旦视图结构确定了,可以屏蔽结构变化对用户的影响
视图指令
创建
CREATE VIEW view_name AS select_statement##Select语句
查看
show tables
修改
ALTER VIEW view_name AS select_statement##Select语句
删除
DROP VIEW view_name
存储过程与存储函数
存储过程和函数就是预先编辑并存储在数据库中的一段SQL语句集合,可以减少数据在数据库和应用服务器之间的传输
存储过程和函数的区别在于函数必须有返回值,前者没有
DELIMITER 用来声明SQL语句的分隔符,默认为;
- 存储过程的创建
CREATE PROCEDURE procedure_name
begin
---sql语句
end;
- 调用
call procedure_name
- 查看
-- 查看指定数据库中的所有储存过程
SELECT name from mysql.proc where db='db_name'
-- 查看存储过程的状态信息
show procedure status
-- 查询某个存储过程的定义
show create procedure procedure_name
- 删除
DROP PROCEDURE procedure_name
存储过程语法
Declare 、set、SELECT… into…、if判断、输入参数,输出参数,case结构、while循环,repeat循环,loop循环、退出条件leave
游标/光标(cursor)
游标是用来储存查询结果集的数据结构
声明游标declare、开启游标open、获取游标fetch、关闭游标close
触发器
触发器是与表有关的数据库对象,在增删改之前或者之后,触发并执行触发器中定义的SQL语句集合
使用别名OLD与NEW来用用触发器中发生变化的记录内容
- 创建触发器
create trigger trigger_name
before/after insert/update/delete
on table_name
begin
triggrt_statement;
end;
- 删除触发器
drop trigger trigger_name
- 查看触发器
show trigger;
MYSQL中有那些锁
-
全局锁
-
经典使用场景为对数据的全库逻辑备份
-
flush tables with read lock
-
-
表级锁
- 每次锁住整张表,锁定颗粒大,发生锁冲突的概率高,并发度最低
-
表锁
- 加锁/解锁 lock tables xxx read/write; unlock tables;
- 表共享读锁(都能读,都不能写)
- 表独占写锁(锁的能读写,其他不能操作该表)
-
元数据锁 (MDL)
- MDL加锁过程是系统自动控制的,主要是维护表元数据的一致性,在表上有活动事务的时候不可以对元数据进行写入操作
- 对一张表增删改查的时候,加共享锁,对表结构进行变更操作的时候,加排他锁
-
意向锁a
- 意向共享锁:与表锁共享锁(read)兼容,与表锁排它锁互斥
- 意向排他锁:与表锁共享锁(read)及排它锁(write)都互斥。意向锁之间不会互斥
-
行级锁
- 锁住对应行的数据,锁粒度最小,发生锁冲突概率最低
- InnoDB行锁是通过对索引上的索引想加锁来实现的,如果不通过索引来检索数据,那么将会对数据中所有记录加锁,升级为表锁
-
行锁
- 共享锁:允许一个事务去读一行,组织其他事物获取相同数据集的排他锁
- 排它锁:
-
间隙锁
-
临键锁
- 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁
- 索引上的等值查询(普通索引),向右遍历最后一个值不满足查询需求时,next-key lock退化为间隙锁
- 索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止
间隙锁唯一目的是防止其然事务插入间隙,间隙锁可以共存
mysql 问题排查都有哪些手段?
- 使用 show processlist 命令查看当前所有连接信息。
- 使用 explain 命令查询 SQL 语句执行计划。
- 开启慢查询日志,查看慢查询的 SQL。
MySQL性能优化
-
硬件和操作系统层面的优化
-
从硬件层面来书,影响MYSQL性能的主要是CPU,可用内存大小,磁盘读写速度,网络带宽
-
从操作系统层面来说,应用文件句柄数,操作系统的网络配置
以上部分的优化主要由DBA和运维工程师去完成
在硬件方面的优化中,重点应该是服务本身多承载的体量,然后提出合理的要求,避免出现资源浪费的一个现象
-
-
架构设计层面的优化
- MYSQL是一个磁盘IO反应非常频繁的关系数据库架,在高并发和高性能的场景中,MYSQL数据库必然会承受巨大的并发压力,对于这些,优化的方式主要可以分为几个部分
- 搭建MYSQL主从集群,单个MYSQL服务容易导致单点故障,一旦服务宕机将会导致依赖MYSQL数据库的应用全部无法响应,主从集群或者主主集群都可以去保证服务的高可用性
- 读写分离设计,在读多写少的场景中,通过读写分离的方案可以去避免读写冲突导致的性能问题
- 引入分库分表的机制,通过分库可以降低单个服务器节能的一个io压力,通过分表的方式,可以去降低单表数据量,从而去提升SQL的查询效率
- 针对热点数据可以引入更为高效的分布数据库,比如说像Redis等,可以很好的缓解MYSQL的访问压力,同时还能提升数据的解锁性能
- MYSQL是一个磁盘IO反应非常频繁的关系数据库架,在高并发和高性能的场景中,MYSQL数据库必然会承受巨大的并发压力,对于这些,优化的方式主要可以分为几个部分
-
MySQL程序酒配置优化
-
MYSQL又是一个互联网大厂检验过的生产级别的成熟数数据库,对于MYSQL数据库本身的优化,一般可以通过MYSQL配置文件my.cnf来完成,比如说MYSQL5.7版本,默认的最大连接数是151个,这个值可以在my.cnf中去修改
-
binlog日志默认是不开启,我们也可以在这个文件中去修改开启
-
缓存BufferPool默认大小配置,这些配置啊一般是和用户的安装环境以及使用场景有关系,这些配置官方只会提供一个默认的配置,具体的情况呢还是得由使用者去根据实际情况去修改
-
关于配置项的修改,需要关注两个层面
-
第一个是配置的作用域,它可以分为会话级别和全局范围
-
第二个是是否支持热加载,
针对这两个点啊,我们需要注意的是的是全局参数的设定,对于已经存在的会话是无法生效的,会话参数的设计,随着会话的销毁而失效
-
第三个是全局类的统一配置建议配置在默认配置文件中,否则重启服务会导致配置失效
-
-
-
SQL执行优化
-
SQL语句上的优化
- SQL的查询一定要基于,索引来进行数据扫描
- 避免索引列上使用函数或者运算符,这样会导致索引失效
- where字句中like %号尽量放置在右边
- 使用索引扫描,联食索引中的列,从左往右,命中越多越好
- 尽可能使用SQL语句用到的索引完成排序
- 查询有效的列信息即可,少用*代替列信息
- 永远用小结果集驱动大结果集
-
慢SQL的定位和排查,我们可以通过慢查询日志和慢查询日志工具分析得到有问题的SQL列表
-
执行计划分析,针对慢SQL,我们可以使用关键字explain来,去查看当前SQL的执行计划,所以重点关注Type,key,Rows,Filterd从而去定位MYSQL执行慢的根本原因再去有目的去进行优化
-
使用的show profile工具,show profile是MYSQL提供的,可以用来分析当前会话中SQL资源消耗情况的工具,可以用于SQL调优的测量,默认情况下show profile是关闭状态,打开之后会保存最近15次的运行结果,针对运行慢的SQL,通过播放工具进行详细分析,可以得到SQL执行过程中所有资源的开销情况以及IO开销,CPU开销,性能开销等
-
1000w数据入库,该怎么设计?
- 一千万的数利用导入功能入库,并且需要实时返回数据库入库成功的返回条数
- 先要对操作的可行性分析 对数据进行分片读取处理
- 效率的提升 CPU的提升,多线程进行并发处理,可以设计锁进行导入
- 数据的读取不是最重要的,最重要的是数据批量如入库,如果导入发现了异常,那么导入就会失败,数据检验也就是重要的一环
- 需要考虑稳定性,可以设计架构设计
Redis
什么是Redis?
Redis是一个开源的非关系型数据库
默认接口为6379
Redis可以存储物五种不同类型的值之间的映射,键类型只能为字符串,值支持五种数据类型:字符串,列表,集合,散列表,有序集合
具有
- 读写高性能
- 使用AOP与ROB两种方式支持数据持久化
- 支持事务
- 数据结构丰富,支持string,hash,set,zset,list
- 支持主从复制
五个大优点
缺点:但是容量受物理内存限制
而且不具备自动容错和恢复功能
较难支持在线扩容
Redis有那些应用场景
- 内存存储和持久化
- 取最新N个数据的操作
- 模拟类似于HttpSession这种需要设定过期时间的功能
- 发布,订阅消息系统
- 定时器,计数器
Redis的如何实现持久化?
Redis提供了AOF与RDB两种持久化机制
RDB是默认的持久化方式,那一定时间将内存的数据以快照的方式保存到硬盘,对应产生的数据为dump.rdb
RDB的优点:
- 只有一个文件,方便持久化
- 容灾性好,文件可以保存到安全的硬盘
- 相对于数据量较大时,比AOF启动效率更高
但是 数据安全性较低,因为是一段时间内的持久化,可能会发生数据丢失
AOF持久化是将Redis每次写命令记录到单独的日志中
AOF的优点:
- 数据安全
- 通过append模式写文件,及时中途服务器宕机也可以通过工具解决数据一致性问题
但是AOF文件比RDB文件大,恢复速度慢,数据量大的时候,启动效率比RDB低
两种方式同时开启时,数据恢复Redis优先选择AOF
什么是Redis的事务?
redis事务就是一次性,顺序性,排他性的执行一个队列的一系列命令
Redis事务的三个阶段
事务开始Multi
命令入队
事务执行Exec
取消事务Discard
watch key 监视一个或者多个key ,事务执行前key被其他命令所改动,事务将会被打断
Redis事务相关命令
Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
-
redis 不支持回滚,“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
-
如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
-
如果在一个事务中出现运行错误,那么正确的命令会被执行。
-
WATCH 命令是一个乐观锁,可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
-
MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
-
EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。
-
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。
-
UNWATCH命令可以取消watch对所有key的监控。
redis是单线程还是多线程
- 无论是什么版本,工作线程就只有一个
- 6.x的高版本出现了IO多线程
- 单线程满足redis串行原则,io线程就可以提高执行速度,并且更高效的压榨系统的性能
在6.x以前的模型,工作线程只有一条,对于多条请求,内核使用多路复用器(epoll)进行管理,然后进行内核的数据读取
在6.x之后的模型,有一条工作线程,若干个io线程,io线程并行,工作线程串行
网卡缓存之后,会在内核中产生队列,然后io线程进行io操作,提高了吞吐量
redis存在线程安全的问题吗?
提出redis单线程串行的点
redis可以保证内部串行,但是不能保证外部业务的顺序
Redis和Mysql如何保证数据一致性
一般情况下,Redis是用来实现应用和数据库之间的一个读操作的缓存层的,主要目的是去减少数据库的iO,还可以提升数据的IO性能
当应用程序需要去读取某个数据的时候,首先会先尝试Redis里面去加载,如果命中了就直接反回,如果没有命中就直接从数据库里面查询,查询到数据之后再把数据缓存到Redis里面,这种架构里面呢,会出现一个问题,就是一份数据同时保存在数据库和Redis里面,当数据发生变化的时候,需要同时去更新Redis和MYSQL,由于更新操做的是有先后顺序的,并且它并不像Mysql中的多表事物操作,可以满足acid的特性,所以就会出现叫数据一致性问题,在这个情况下,能够选择的方法只有几种
-
先更新数据库再更新缓存
如果先根据数据库再更新缓存,再更新缓存,那么如果缓存更新失败就会导致数据库和Redis的数据是不一致的,
-
先删除缓存再更新数据库,
如果是先删除缓存再更新数据库,理想情况下是应用下次访问Redis的时候,发现Redis里面的数据是空的,那么就会从数据库加载保存到Redis里面,数据理论上是一致的,但是在极端情况下由于删除Redis和更新数据库,这两个操作并不是原子操作,所以在这个过程中,如果出现其他线程来访问,还是会存在数据不一致的问题
-
如果需要在极端情况下仍然去保证 Redis和Mysql的数据一致性,就只能采用最终一致性的方案,比如基于RocketMQ的可靠性消息通信来实现数据的最终一致性,还可以直接通过Canal组件监控Mysql里面的binlog日志,把更新后的数据同步到Redis里面,因为这里是基于最终一致性来实现的,如果业务场景不能去接受数据的短期不一致性,那么就不能使用这样的方案
redis 如何做内存优化?
- 设置内存上限
- 配置内存回收策略
- 键值对优化
- 共享对象池
- 字符串优化
- 编码优化
- 控制键的数量
缓存雪崩和缓存穿透的理解以及如何避免?
缓存雪崩
现象:存储在缓存里面,大量数据在同一个时刻全部过期,原本缓存组件能够扛住了大部分流量全部请求到了数据库,从而呢导致数据库压力增加,造成数据库服务器的崩溃的一种现象
导致缓存雪崩的原因有几个方面
一个是缓存中间件宕机,当然可以对缓存中间件做高可用集群来避免,其实就是缓存中里面大部分key可以都设置的相同的过期时间,导致同一时刻这些key都过期了,所以在这样一个情况下呢,可以在失效时间里面去增加1~5分钟的随机值,从而去避免同时失效的一个问题,
缓存穿透
现象:表示的是短时间内有大量的不存在的key请求到应用程序里面,而这些不存在的key呢在缓存里面又找不到,从而去导致全部的请求,全部穿透到的数据库,造成数据库的压力增加,我认为这个长期的核心问题是针对于缓存的一种攻击行为,因为正常的业务里面即便是出现这样一个不存在key的情况,由于缓存的不断预热,影响也不会很大,而攻击行为呢就需要去具备时间的一个持续性,而只有key确定在数据库里面不存在的情况下,才能达到这样一个目的,
所以在这个问题下有两个方法可以去解决
-
第1个是把无效的key保存到Redis里面,并且设置一个特殊的值,比如说像“null”字符串,这样的话下次再来访问的时候就不会去查数据库了,(但是如果攻击者不断的用随机的不存在的key来访问也还是会存在相同的问题)
-
使用布隆过滤器来实现,在系统启动的时候,我们可以把目标数据全部缓存到布隆过滤器里面,当攻击者去使用不存在的key来请求的时候先到布隆过滤器里面去进行查询,如果不存在就意味着这个key,肯定在数据库里面也不存在,所以这个时候就不会去访问数据库
布隆过滤器呢还有个好处
它采用的是bitmap,位图来进行数据存储,所以它的占用内存空间是很小的
不过呢,在我看来啊,这个问题真的有点过于放大他所带来的影响,当然也是要考虑,
首先我认为为什么放大呢,是因为
-
第一个在一个成熟的系统里面,对于比较重要的热点数据必然会有一个专门的缓释系统来维护,同时啊他的过期时间的维护,必然和其他的业务的key会有一定的区别,
-
而且对于非常重要的场景,我们还会设计多级缓存的一个实现
-
其次啊,即便是触发了缓存雪崩,那么数据库本身的容灾能力也并没有那么脆弱,数据库的“主从”“双组”“读写分离”这些策略都可以很好的去缓解并发流量,
-
最后呢数据库本身也有最大连接数的限制,超过限制的请求会被拒绝,
-
再结合熔断机制也能够很好的去保护数据库系统,最多就是造成部分用户体验不好而已,
-
另外在程序设计上,为了避免缓存未命中导致大量请求穿透的数据库的问题,我们还可以在访问数据库这个环节里面去加锁,虽然影响了性能,但是对整个系统是安全的
总而言之办法有很多,具体选择哪种方式还要看具体的业务场景
redis如何删除过期key
过期的策略有三种;定时过期,惰性过期,定期过期
可以使用expire设置key的过期时间,对于处理过期一般就两种策略
- 定时清理
- 有请求,发现过期再去清理更新
redis 淘汰策略有哪些?
volatile-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(驱逐):禁止驱逐数据
注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的 lru、ttl 以及 random 是三种不同的淘汰策略,再加上一种 no-enviction 永不回收的策略。
使用策略规则:
(1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
(2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
Redis的内存淘汰算法和原理是什么?
Redis里面的内存淘汰策略呢是指,当内存的使用率达到了max memory的上限的时候,他的一种内存释放的一个行为
Redis里面提供了很多种内存的淘汰算法,归纳起来呢主要有4种,
-
第1种是随机:随机移除某个key
-
第2种是TTL算法:就是在设置的过期时间的键里面呢,去找到更早过期时间的key进行有限移除
-
第3个是LRU算法去移除最近很少使用的key
- LRU是一种比较常见的内存淘汰算法,在Redis里面它会维护一个大小为16的候候选池,这个候选池里面的数据会根据时间进行排序,每一次随机抽取5个key放到这个候选池里面,当候选词满了以后,访问的时间间隔最大的key,就会从候选词里面取出来淘汰掉,通过这一个设计,就可以把真实的最少访问的key从内存里面淘汰内存里面淘汰
- 但是这样还是会存在一个问题,假如一个key很长时间没有访问,但是最近一次偶然被访问到那么,LRU就会认为这是一个热点key,不会被淘汰,所以在Redis4里面增加了一个LFU的算法
- LUF增加了访问频率这样一个维度来统计数据的热点情况
-
第4个是LFU算法,那么他跟LRU算法是类似的
- LFU的主要设计是使用了两个双向列表去形成一个二维的双向列表,一个是用来保存访问频率,另一个是用来保存访问频率相同的所有的元素,当添加元素的时候啊,访问频次默认为1,于是找到相同频次的节点,然后添加到相同的频率节点对应的双向列表的头部,当元素被访问的时候呢,就会增加对应T的访问频率,并且把当前访问的节点移动到下一个频次的节点
- 有可能会出现某个数据前期的访问次数很多,然后后续就一直不使用了,如果单纯按照这样的一个访问频次来进行淘汰的话,那么这个T就很难被淘汰掉,所以啊,在LFU的算法里面去通过了使用频率和上次访问时间来标记数据的这样一个热度,如果某个数据有如何写,那么就增加访问的频率,如果一段时间内这个数据没有读写,那么就减少访问频率,
- 通过LFU算法改进之后,就可以真正达到非热点数据的淘汰,
- LFU也有缺点,相比LRU算法呢,LFU增加了访问频次的一个维护以及实现的复杂度,要比LRU更高
主从不一致的问题
redis是弱一致性的,异步的同步
锁不能使用主从
在配置中提供必须要有多少个client链接能同步,配置同步一直,趋向一致性
JavaWeb
jsp 和 servlet 有什么区别?
Servlet
一种服务器端的Java应用程序,由 Web 容器加载和载管理,用于生成动态 Web 内容,负责处理客户端请求
Jsp
是 Servlet 的扩展,本质上还是 Servlet,每个 Jsp 页面就是一个 Servlet 实例,Jsp 页面会被 Web 容器编译成 Servlet,Servlet 再负责响应用户请求
jsp 有哪些内置对象?作用分别是什么?
jsp共有以下9个内置对象:
1.request 客户端请求,此请求会包含GET/POST请求的参数
2.response 网页传回客户端的回应
3.pageContext 网页的属性是在这里管理
4.session 请求有关的会话期
5.application servlet正在执行的内容
6.out 用来传送回应的输出
7.config servlet的架构部分
8.page jsp页面网页本身
9.exception 针对错误网页,未捕捉的例外
说一下 jsp 的 4 种作用域?
名称 | 作用域 |
---|---|
application | 在所有应用程序中有效 |
session | 在当前会话中有效 |
request | 在当前请求中有效 |
page | 在当前页面有效 |
session 和 cookie 有什么区别?
存储位置不同,Cookie在浏览器端存储,Session在服务器端存储;
存储容量不同,Cookie存储容量很小,Session存储容量可以很大;
安全性不同,Cookie安全性较低,Session安全性很高;
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用cookie
如果客户端禁止 cookie 能实现 session 还能用吗?
一般默认情况下,在会话中,服务器存储 session 的 sessionid 是通过 cookie 存到浏览器里。
如果浏览器禁用了 cookie,浏览器请求服务器无法携带 sessionid,服务器无法识别请求中的用户身份,session失效。
但是可以通过其他方法在禁用 cookie 的情况下,可以继续使用session。
- 通过url重写,把 sessionid 作为参数追加的原 url 中,后续的浏览器与服务器交互中携带 sessionid 参数。
- 服务器的返回数据中包含 sessionid,浏览器发送请求时,携带 sessionid 参数。
- 通过 Http 协议其他 header 字段,服务器每次返回时设置该 header 字段信息,浏览器中 js 读取该 header 字段,请求服务器时,js设置携带该 header 字段。
如何避免 sql 注入?
严格限制 Web 应用的数据库的操作权限,给连接数据库的用户提供满足需要的最低权限,最大限度的减少注入攻击对数据库的危害
校验参数的数据格式是否合法(可以使用正则或特殊字符的判断)
对进入数据库的特殊字符进行转义处理,或编码转换
预编译 SQL(Java 中使用 PreparedStatement),参数化查询方式,避免 SQL 拼接
发布前,利用工具进行 SQL 注入检测
报错信息不要包含 SQL 信息输出到 Web 页面JVM
什么是 XSS 攻击,如何避免?
这个词实际上是CSS(Cross Site Scripting),但它与CSS同名。所以名字是XSS。跨站点脚本攻击类似于上面提到的CSRF。实际上,原则是将一段JavaScript代码注入网页
XSS主要分为两大类:非持久型攻击、持久型攻击。
不管是用户端从任何的输入到任何输出都进行过滤,转义,让攻击者的代码注入不能识别,就可以避免攻击了
什么是 CSRF 攻击,如何避免?
CSRF(Cross-site request forgery)跨站请求伪造
目前防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。
并发
并行和并发有什么区别?
- 并发: 同⼀时间段,多个任务都在执⾏ (单位时间内不⼀定同时执⾏);
- 并⾏: 单位时间内,多个任务同时执⾏。
线程和进程的区别?
进程:
- 进程是程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的。系统运⾏⼀个程序即是⼀个进程从创建,运⾏到消亡的过程。
- 在 Java 中,当我们启动 main 函数时其实就是启动了⼀个 JVM 的进程,⽽ main 函数所在的线程就是这个进程中的⼀个线程,也称主线程。
线程:
- 线程与进程相似,但线程是⼀个⽐进程更⼩的执⾏单位。⼀个进程在其执⾏的过程中可以产⽣多个线程。与进程不同的是同类的多个线程共享进程的堆和⽅法区资源,但每个线程有⾃⼰的程序计数器、虚拟机栈和本地⽅法栈,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程。
区别:
- 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
- 进程拥有共享的资源,如内存空间等,供其内部的线程共享
- 进程间通信较为复杂
- 同一台计算机的进程通信称为 IPC(Inter-process communication)
- 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
- 线程通信相对简单,它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
- 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
创建线程的方式
-
直接使用 Thread类创建
-
使用 Runnable 类配合 Thread类创建
-
把【线程】和【任务】(要执行的代码)分开
-
Thread 代表线程Runnable 可运行的任务(线程要执行的代码)
-
-
使用FutureTask类(Thread类的任务为管理器) 配合 Thread类创建//实现一个Callable接口(可以返回返回值)
说一下 runnable 和 callable 有什么区别?
相同点
1、两者都是接口;(废话)
2、两者都可用来编写多线程程序;
3、两者都需要调用Thread.start()启动线程;
不同点
1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
注意点
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
线程有哪些状态?
- 新建状态(new)
- 就绪状态(thread.start())
- 运行状态(Runnable)
- 等待状态
- 超时等待状态
- 阻塞状态
- 终止状态
sleep() 和 wait() 有什么区别?
调用sleep方法的线程不会释放对象锁,而调用wait() 方法会释放对象锁
notify()和 notifyAll()有什么区别
notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池
volatile关键字有什么用?它的实现原理是什么?
volatile关键字有两个作用
- 可以保证多线程环境下共享变量的可见性
- 通过增加内存屏障防止多个指令之间的重排序
在我的理解中,可见性是指当一个线程对于共享变量的修改,其他线程可以立刻看到线程修改之后的值
其实这个可见性,本质上是有几个方面造成的
-
第一个是CPU层面的高速缓存
- 在CPU里面设计了三级缓存去解决CPU运算效率和内存IO效率的问题,但是带来了缓存一致性的问题
- 在多线程并行的情况下,缓存一致性的问题就会导致可见性的问题
- 所以对于增加了volatile关键字的一个修饰的共享变量,JVM虚拟机会自动增加一个#LOCK汇编指令,指令会根据不同的CPU型号去自动添加总线锁,或者缓存锁
总线锁:他锁定的是CPU的前端总线,从而导致只能有一个线程和内存通信,这样就避免了多线程并发造成的可见性问题
缓存锁:缓存锁是对总线锁的一个优化,因为总线锁的CPU使用效率大幅度下降,所以缓存锁只针对CPU三级缓存中目标数据去加锁,而缓存锁是使用MESI缓存一致性协议来实现的
-
第二个是指令重排序
- 重排序就是指令的编写顺序和执行顺序是不一致的从而在多线程环境下导致可见性问题
- 指令重排序本质上是一种性能优化的手段,他来自多个层面,CPU层面,针对MESI协议更进一步的优化,去提升CPU的一个利用率,所以他引用了一个叫StoreBuffer的机制,这个优化机制会导致CPU的乱序执行
- 为了避免这样的问题,CPU提供了内存屏障锁,上层的应用可以在合适的地方插入内存屏障,去避免CPU指令重排序的一个问题
-
第三个是编译器层面优化
- 编译器在编译的过程中,在不改变单线程语义和程序正确性的前提下,对指令进行合理的重排序,从而去优化整体的一个性能
- 所以对于共享变量增加了volatile关键字,那么编译器层面,就不会去触发JVM优化,同时在JVM层面他会插入内存屏障指令来去避免重排序的问题
当然除了volatile关键字以外,从JDK5开始,JMM就使用了一种Happens-Before的模型去描述多线程的可见性的一个关系,也就是如果两个操作之间具备Happens-Before关系那么就意味着这两个操作具备可见性的一个关系,不需要再额外考虑增加volatile关键字来提供可见性的一个保障
创建线程池有哪几种方式?
-
定长线程池:newFixedThreadPool 每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程
-
可缓存的线程池:newCatchThreadPool 如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制
-
定长线程池:newScheduledThreadPool 可执行周期性的任务
-
单线程的线程池:newSingleThreadExecutor 线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行
-
单线程可执行周期性任务的线程池:newSingleThreadScheduledExecutor
-
任务窃取线程池:newWorkStealingPool 不保证执行顺序,适合任务耗时差异较大
-
最原始的线程池:ThreadPoolExecutor()
线程池都有哪些状态?
- RUNNING 这是最正常的状态,接受新的任务,处理等待队列中的任务。线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0
- SHUTDOWN 不接受新的任务提交,但是会继续处理等待队列中的任务。调用线程池的shutdown()方法时,线程池由RUNNING -> SHUTDOWN。
- STOP 不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。调用线程池的shutdownNow()方法时,线程池由(RUNNING or SHUTDOWN ) -> STOP
- TIDYING 所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。因为terminated()在ThreadPoolExecutor类中是空的,所以用户想在线程池变为TIDYING时进行相应的处理;可以通过重载terminated()函数来实现。
- TERMINATED 线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED
线程池中 submit()和 execute()方法有什么区别
execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
execute() 没有返回值;而 submit() 有返回值
submit() 的返回值 Future 调用get方法时,可以捕获处理异常
ThreadLocal以及实现原理
首先ThreadLocal是一种线程隔离机制,它提供了多线程环境下,对于共享变量访问的一个安全性
在多线程访问共享变量这个场景里,一般情况下的我们的解决办法是对于共享变量去加锁,所以保证在同一个时刻只有一个线程能够对共享变量进行更新,并且基于happens -before规则里面的锁监视器规则,又能够保证数据修改之后对其他线程是可见的,但是加锁会带来性能上的下降,所以ThreadLocal用了一种空间换时间的一个设计思想,也就是说在每个变量里面都有一个容器来存储共享变量的一个副本,然后每个线程只对自己的变量副本来做更新操作,这样的话既解决了现成的安全问题,又避免了多线程竞争所的一个开销
ThreadLocal的一个具体实现原理是在Thread类里面有一个成员变量叫ThreadLocalMap,他专门用来存储当前限量的共享变量的一个副本,后续这个,去这个线程对共享边的一个操作呢,都是从这样一个ThreadLocalMap里面去进行变更的,不会影响全局共享变量的一个值,从而去实现数据的一个隔离
死锁的发生原因和怎么避免
产生死锁的必要条件:互斥条件、请求和保持条件、不剥夺条件、环路等待条件
死锁简单来说就是两个或者两个以上的线程在执行的过程中去争夺同样一个共享资源造成的相互等待的一个现象,如果没有外部的干预,线程会一直阻塞,无法往下去执行,这样一直处于相互等待资源的线程,我们称为死锁线程。
导致死锁的条件有4个,也就是说这4个条件,同时满足就会产生死锁。
- 互斥条件:共享资源,X和Y只能被一个线程占用 ,
- 请求和保持条件,线程T1如果已经取得了共享资源X,在等待共享资源Y的时候,不释放共享资源X
- 不可抢占条件,就是其他线程不能抢占线程T1占有的资源
- 循环等待条件,线程T1等待线程T2占有的资源,线程T2等待线程T1占有的资源就叫循环等待
导致死锁之后呢,只能通过人工干预来解决,比如说重启服务或者“kill”掉这这个线程,所以我们只能在写代码的时候去规避可能出现的死锁问题,而按照死锁发生的4个条件我们只需要破坏其中的任何一种就可以去解决它,但是
- 互斥条件是没有办法被破坏的,因为它是互斥锁的基本约束,而其他的三个条件都有办法来破坏
- 对于请求和保持这个条件,我们可以一次性申请所有的资源,这样的话就不存在锁要的等待
- 对于不可抢占条件,占用部分资源的线程在进一步申请其他资源的时候,如果申请不到,我们可以主动去释放它占有的资源,这样不可抢占条件就会被破坏掉了,
- 对于循环等待条件,可以按序申请资源来预防,所谓按需申请,是指资源是有限性顺序的,申请的时候可以先申请资源的序号小的然后再去申请资源序号大的,这样线性化之后呢自然就不存在循环了
请你谈一下CAS机制?
JVM
说一下 jvm 的主要组成部分?及其作用?
类加载子系统:类加载器
运行时数据区域:方法区、虚拟机栈、本地方法栈、堆、程序计数器
执行引擎:JIT即时编译器、垃圾回收器
本地库接口
本地方法库
首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能
一个对象的创建有多少方法
- new
- Class 的 newInstance方法
- Constructor 的 newInstance方法
- clone方法
- 反序列
类什么时候会被加载?
- 对象创建的时候
- 父类会在子类创建对象的时候加载
- 直接调用静态成员
一个对象创建的主要流程
虚拟机遇到一条new 指令时,先检查常量池是否以及加载相应的类,如果没有,需要立刻去加载目标类,然后去调用目标类的构造器去完成初始化,目标类的加载是通过类加载器来实现的,主要就是把一个类加载到内存里面,初始化的过程,这个步骤主要是对目标类里面的静态变量,成员变量,静态代码块进行初始化,当目标内被初始化以后就可以从常量池里面去找到对应的类源信息
类加载初始化通过后,接下来分配内存。若java堆内存是绝对的规整,使用“指针碰撞”方法分配内存;如果不规整,就从空心列表中分配,叫“空闲列表”方式。
指针碰撞:分配内存是将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离
空闲列表:需要有虚拟机维护一个列表来记录那些内存是可用的,在分配到时候可以从列表中查询到足够大堆内存分配给对象,并在分配后更新列表记录
划分内存时需要考虑一个问题-并发,也有两种方式:CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。
然后内存空间初始化操作,JVM会把目标对象里面的普通成员变量初始化为零,至比如说int类型初始化为零,String类型初始化为“null”,这一步操作主要是要保证对象里面的实例字段,不用初始化就可以直接使用,程序能够直接获取这些字段对应的数据类型的0值
之后JVM还需要对目标对象的对象头做一些设置,比如对象所属的内源信息,对象的GC分代年龄,HashCode,锁标记等,完成这些步骤以后呢,对于JVM来说新对象的创建工作已经完成了,但是对于Java语言来说,对象创建才算刚刚开始
接下来要做的啊,就是执行目标对象内部生成的方法初始化成员变量的值,执行构造块,最后调用目标对象的构造方法去完成对象创建。其中啊,方法是Java文件编译之后,在字节码文件里生成的,它是一个实例构造器,这个构造器里面会把构造块,变量初始化,调用父类构造器等这样一些操作组织在一起,调用方法能完成这一系列动作
对象的访问定位有哪几种方式
java程序需要通过JVM栈上的引用访问堆中具体的对象。对象的访问方式取决于JVM虚拟机实现,主流访问方式有句柄和直接指针
指针: 指向对象,代表一个对象在内存中的起始地址。
句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。
内存溢出和内存泄露的区别
java会存在内存泄露吗?简单描述
java是有GC回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除
但是,即使这样,java还是存在内存泄露的情况:长生命周期的对象持有短生命周期的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收;
说一下 jvm 运行时数据区?
JVM运行时数据区可划分为,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈,方法区和堆。
其中方法区和堆属于线程之间共享的,程序计数器(PC寄存器),Java虚拟机栈,本地方法栈属于线程私有的。
说一说栈与堆的区别
栈使用的是数据结构中的栈,先进后出的远侧,物理地址分配是连续的所以性能快
栈内存放的是局部变量,操作树栈,返回结果。更关注的是程序方法的执行
栈是连续的,所以分配内存的大小要在编译期就确认,大小固定
栈只对于线程是可见的,所以也是线程私有的。他的生命周期和线程相同
堆物理地址分配对象是不连续的,可能性能慢一些
堆存放的是对象的实例和数组,该区更关注的是数据的存储
堆是不连续的,所以分配内存是在运行期就确定的,大小不固定,一般堆大小远远大于栈
堆对于整个应用程序的都是可见的
静态变量存在方法区
静态的对象还是放在堆
JVM类加载过程
加载、验证、准备、解析,初始化,使用,卸载
什么是双亲委派模型?
在类加载过程中,对于类文件的加载顺序
总共有四种类加载器
启动类加载器、扩展类加载器、应用类加载器、自定义加载器
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器(注意,上图已经指出,这里的父子关系是组合关系而非继承关系)去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。
说一下 jvm 有哪些垃圾回收器?
新生代收集器:
- Serial(新生代单线程收集)
- ParNew(新生代并行收集)
- Parallel Scavenge(新生代并行收集器,追求高吞吐量)
吞吐量 = 用户线程时间/(用户线程时间+GC线程时间)
老年代收集器:
- Serial Old(老年代单线程收集器)
- CMS(老年代并行收集器)
- Parallel Old(老年代并行收集器,吞吐量优先)
堆内存垃圾收集器:G1(Java堆并行收集器)
详细介绍一下 CMS 垃圾回收器?
CMS 处理过程有七个步骤:
- 初始标记(CMS-initial-mark) ,会导致swt;
- 并发标记(CMS-concurrent-mark),与用户线程同时运行;
- 预清理(CMS-concurrent-preclean),与用户线程同时运行;
- 可被终止的预清理(CMS-concurrent-abortable-preclean) 与用户线程同时运行;
- 重新标记(CMS-remark) ,会导致swt;
- 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
- 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;
说一下 jvm 有哪些垃圾回收算法?
1.标记-清除算法
2.复制算法
3.标记-整理算法
4.分代收集算法
怎么判断对象是否可以被回收?
- 引用计数算法(不能解决循环引用的问题)
- 根可达性分析算法
Mybatis
mybatis 中 #{}和 ${}的区别是什么?
#{}是预编译处理
$ 符号一般用来当作占位符
Spring
什么是spring
spring是一个轻量级java开发框架致力于让程序员开发更加高效 它
- 方便解耦,简化了开发
- 提供了面向切面编程的支持
- 声明事务的支持
- 方便集成各种优秀框架
但是spring是一个轻量级框架却给人大而全的感觉,而且非常依赖反射,反射影响性能
spring的核心特性
IOC与AOP
什么是Spring IOC容器,以及其底层实现
控制反转:原来的对象是由使用者来进行控制,有了spring之后,可以把整个对象交给spring来帮我们进行管理
DI:依赖注入,把对应的属性的值注入到具体的对象中,@Autowired,populateBean完成属性值的注入
容器:存储对象,使用map结构来存储,在spring中一般存在三级缓存,singletonObjects存放完整的bean对象,整个bean的生命周期,从创建到使用到销毁的过程全部都是由容器来管理(bean的生命周期)
- 一般聊ioc容器的时候要涉及到容器的创建过程(beanFactory,DefaultListableBeanFactory) ,向bean工厂中设置一些参数( BeanPostProcessor,Aware接口的子类)等等属性
- 加载解析bean对象,准备要创建的bean对象的定义对象beanDefinition,(xml或者注解的解析过程)
- beanFactoryPostProcessor的处理,此处是扩展点, PlaceHolderConfigurSupport,ConfigurationClassPostProcessor
- BeanPostProcessor的注册功能,方便后续对bean对象完成具体的扩展功能
- 通过反射的方式讲BeanDefinition对象实例化成具体的bean对象
- bean对象的初始化过程(填充属性,调用aware子类的方法,调用BeanPostProcessor前置处理方法,调用init-mehtod方法,调用BeanPostProcessor的后置处理方法)
- 生成完整的bean对象,通过getBean方法可以直接获取
- 销毁过程
依赖注入的实现方式
接口注入
Setter方法注入
构造器注入
容器配置bean的方式
1.纯配置文件
2.配置文件加注解@Component+
3.纯注解@Configuration +@Bean
Spring支持的几种bean的作用域
(1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。
(2)prototype:为每一个bean请求提供一个实例。
(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
(5)global-session:全局作用域,global-session和Portlet应用相关。
spring 事务实现方式有哪些?
(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
(2)基于 TransactionProxyFactoryBean的声明式事务管理
(3)基于 @Transactional 的声明式事务管理
(4)基于Aspectj AOP配置事务
spring中的循环依赖是怎么解决的
如果在代码中把两个或者多个Bean相互之间去持有对方的引用,就会发生循环依赖,循环依赖的会导致注入出现死循环,这是spring发生循环依赖的一个原因,循环依赖有三种形态
- 第一种是互相依赖,A依赖B, B又依赖A,它们之间形成了一个循环依赖。
- 第二种是三者间依赖也就是A依赖B,B依赖C,C又依赖A,形成了一个循环依赖
- 第三种是自我依赖也就是说A依赖A形成的循环
Spring设计了三级缓存去解决情况应该的问题,当我们去通过getBean去获得一个对象实例的时候,Spring会先从一级缓存去找,如果发现一级缓存中没有找到,就去二级缓存的去找,如果一二级缓存都没有找到,意味着这个Bean还没有实际化,于是容器会去实例化这个Bean,而这个初始化Bean,我们认为它叫早期Bean,于是会把这个目标Bean人放入到二级缓存,同时啊加入一个标记是表示它是否存在循环依赖,如果不存在就把这个Bean放入二级缓存,否则会标记这个Bean的存在循环依赖,然后在等待下一次轮循的时候去赋值,就是解析Autowired注解,Autowired注解对赋值完后,会将目标Bean存一键缓存。
这个可以做一个总结。Spring一级缓存存放所有成熟的Bean,二级缓存能存放所有的早期Bean,先取一级缓存再取二期缓存。
第三级缓存的作用
三级缓存呢是用来存储代理Bean啊,当调用getBean方法的时候,发现目标Bean需要通过代理工厂来创建,这个时候,会把创建好的实例保存到三级缓存,最终也会把复制好的Bean同步到一级缓存
Spring在哪些情况下是无法去解决循环依赖的问题
有4种情况下的情况应该是无法被解决
- 多实例Bean通过setter注入的时候不能解决
- 构造器注入Bean的情况下不能解决
- 单例的代理Bean,通过Setter的注入的情况下
- 设置@DependsOn的Bean
Spring Bean的生命周期
Spring Bean生命周期,大致可以分为5个阶段
分别是创建前准备,准备实例化,依赖注入,容器缓存和销毁实例阶段,
- 阶段创建前准备,这个阶段的主要作用是,Bean在开始加载之前,要从上下文和一些配置中去解析并且查找bean有关的一些扩展的实现,比如说像“init-method”,容器在初始化bean的时候调用的一个方法,“Destroy method”容器在销毁bean的时候调用那些方法以及BeanFactoryPostProcessor,这一类的病bean,加载过程中的一些前置和后置的一些处理扩展实现,这些类或者配置啊,其实是spring提供给开发者用的,就实现并加载过程中的一些扩展,在很多的Spring集成的中间件中也比较常见
- 创建实例阶段,这个阶段主要作用的是通过反射去创建bean的实例对象,并且会扫描和解析bean声明的一些属性
- 依赖注入,如果被实例化的bean则需要对这些依赖的bean,则需要对这些依赖的bean进行对象注入,比如常见的@AutoWired以及Setter注入等这样的一些配置形式,同时在这个阶段,会触发一些扩展的调用,比如常见的扩展类BeanPostProcessors用来去实现Bean初始化前后的扩展前调
- 容器缓存阶段,容器缓存阶段主要的作用是把bean保存到容器以及生命的缓存中,到了这个阶段的bean就可以被开发者去使用,这个阶段涉及到的一些操作,常见的像init-method,这个属性配置一些方法或者这个阶段会被调用,以及像BeanPostProcessors的后置处理器方法,也会在这个阶段被触发
- 销毁实例阶段,当Spring的应用上下文被关闭的时候,那么这个上下文中所有的Bean呢会被销毁,如果存在Bean实现了像DisposableBean接口或者配置了destory-method属性的一些方法,会在这个阶段被调用
- 实例化bean:反射的方式生成对象
- 填充bean的属性: populateBean(),循环依赖的问题(三级缓存)
- 调用aware接口相关的方法: invokeAwareMethod(完成BeanName,BeanFactory,BeanClassLoader对象的属性设置)
- 调用BeanPostProcessor中的前置处理方法:使用比较多的有(ApplicationContextPostProcessor,设置ApplicationContext,Environment,ResourceLoader,EmbeddvalueResolver等对象)
- 调用initmethod方法: invokeInitmethod(),判断是否实现了initializingBean接口,如果有调用afterPropertiesSet方法,没有就不调用
- 调用BeanPostProcessor的后置处理方法: spring的aop就是在此处实现的,AbstractAutoProxyCreator注册Destuction相关的回调接口
- 获取到完整的对象,可以通过getBean的方式来进行对象的获取
- 销毁流程,1;判断是否实现了DispoableBean接口,2,调用destroyMethod方法
BeanFactory和ApplicationContext的区别
BeanFactory
- 它使用懒加载
- 它使用语法显式提供资源对象
- 不支持国际化
- 不支持基于依赖的注解
ApplicationContext
- 它使用即时加载
- 它自己创建和管理资源对象
- 支持国际化
- 支持基于依赖的注解
拦截器与过滤器
过滤器(Filter)
过滤器,是在java web中将你传入的request、response提前过滤掉一些信息,或者提前设置一些参数。然后再传入Servlet或Struts2的 action进行业务逻辑处理。比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入Servlet或Struts2的action前统一设置字符集,或者去除掉一些非法字符。
拦截器(Interceptor)
拦截器,是面向切面编程(AOP,Aspect Oriented Program)的。就是在你的Service或者一个方法前调用一个方法,或者在方法后调用一个方法。比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。
通俗理解:
- 过滤器(Filter):当你有一堆东西的时候,你只希望选择符合你要求的某一些东西。定义这些要求的工具,就是过滤器。(理解:就是一堆字母中取一个B)
- 拦截器(Interceptor):在一个流程正在进行的时候,你希望干预它的进展,甚至终止它进行,这是拦截器做的事情。(理解:就是一堆字母中,干预它,通过验证的少点,顺便干点别的东西)
使用场景
SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
- 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
- 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
- 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
- 通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
- OpenSessionInView:如hibernate,在进入处理器打开Session,在完成后关闭Session。
拦截器与过滤器的区别
- 拦截器是基于java的反射机制的,而过滤器是基于函数的回调。
- 拦截器不依赖于servlet容器,而过滤器依赖于servlet容器。
- 拦截器只对action请求起作用,而过滤器则可以对几乎所有的请求起作用。
- 拦截器可以访问action上下文、值、栈里面的对象,而过滤器不可以。
- 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。
- 拦截器可以获取IOC容器中的各个bean,而过滤器不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
Spring controller中使用了静态变量会出现什么问题,如何避免这些问题?
静态变量不属于对象的属性,而是属于类的属性,Spring是基于对象的属性进行依赖注入的,所以使用静态变量后对静态变量进行注入,会报空指针异常
解决方式有三种:
-
xml方式 在bean中添加init-method关键字为init
-
使用getBean方法 static修饰对象的时候,使用LocalContextFactory.getInstance().getBean方法赋值
-
使用注解@PostConstruct方法实现
编写一个方法
init()
,并标注注解@PostConstruct
,意思就是在完成构造函数实例化后就调用该方法,该方法会对对象实例化。 -
set方法上添加@Autowired注解,类定义上添加@Component注解;
Bean Factory与FactoryBean有什么区别?
- 相同点:都是用来创建bean对象的
- 不同点:使用BeanFactory创建对象的时候,必须要遵循严格的生命周期流程,太复杂了,,如果想要简单的自定义某个对象的创建,同时创建完成的对象想交给spring来管理,那么就需要实现FactroyBean接口了
isSingleton:是否是单例对象
getObjectType:获取返回对象的类型
getObject:自定义创建对象的过程(new,反射,动态代理)
Spring中用到的设计模式?
- 单例模式: bean默认都是单例的
- 原型模式:指定作用域为prototype工厂模式: BeanFactory
- 模板方法: postProcessBeanFactory,onRefresh,initPropertyValue
- 策略模式: XmlBeanDefinitionReader,PropertiesBeanDefinitionReader
- 观察者模式: listener,event,multicast
- 适配器模式: Adapter
- 装饰者模式:BeanWrapper
- 责任链模式:使用aop的时候会先生成一个拦截器链
- 代理模式:动态代理
- 委托者模式: delegate
Spring AOP的实现场景有哪些
事务管理、安全检查、权限控制、数据校验、缓存、对象池管理等
Spring AOP的底层实现原理
aop是ioc的一个扩展功能,先有的ioc,再有的aop,只是在ioc的整个流程中新增的一个扩展点而已:BeanPostProcessor总: aop概念,应用场景,动态代理
bean的创建过程中有一个步骤可以对bean进行扩展实现,aop本身就是一个扩展功能,所以在BeanPostProcessor的后置处理方法中来进行实现
- 代理对象的创建过程(advice,切面,切点
- 通过jdk或者cglib的方式来生成代理对象
- 在执行方法调用的时候,会调用到生成的字节码文件中,直接回找到DynamicAdvisoredinterceptor类中的intercept方法,从此方开始执行
- 根据之前定义好的通知来生成拦截器链
- 从拦截器链中依次获取每一个通知开始进行执行,在执行过程中,为了方便找到下一个通知是哪个,会有一cglibMethodInvocation的对象,找的时候是从-1的位置一次开始查找并且执行的。
JDK动态代理和CGLIB动态代理
1.实现的接口不一样
JDK动态代理实现InvocationHandler接口
CGLIB实现MethodInterceptor接口
2.使用的方法不一样
JDK的方法:Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this)
method.invoke(target,args)
利用所代理方法所实现的接口来进行获取所需代理类的方法与变量
CGLIB的方法 Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
methodProxy.invokeSuper(o,objects)
利用继承所需代理的方法来进行获取所需代理类的方法与变量
3.要求不一样
JDK被代理的对象必须要实现接口
CGLIB被代理的对象最好不要声明成final
4.虽然方法不一样,但是两者的最后结果一样
Spring的事务是如何回滚的?
spring的事务管理是如何实现的?
- 总: spring的事务是由aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不是通过通知来实现的,而是通过一个TransactionInterceptor来实现的然后调用invoke来实现具体的逻辑
- 分:
- 先做准备工作,解析各个方法上事务相关的属性,根据具体的属性来判断是否开始新事务
- 当需要开启的时候,获取数据库连接,关闭自动提交功能,开起事务
- 执行具体的sql逻辑操作
- 在操作过程中,如果执行失败了,那么会通过completeTransactionAfterThrowing看来完成事务的回滚操作,回滚的具体逻辑是通过doRollBack方法来实现的,实现的时候也是要先获取连接对象,通过连接对象来回滚
- 如果执行过程中,没有任何意外情况的发生,那么通过commitTransactionAfterReturning来完成事务的提交操作,提交的具体逻辑是通过doCommit方法来实现的,实现的时候也是要获取连接,通过连接对象来提交
- 当事务执行完毕之后需要清除相关的事务信息cleanupTransactionInfo如果想要聊的更加细致的话,需要知道TransactionInfo,TransactionStatus
spring事务的传播
传播特性有几种? 7种
Required,Requires_new,nested,Support,Not_Support,Never,Mandatory
某一个事务嵌套另一个事务的时候怎么办?
A方法调用B方法,AB方法都有事务,并且传播特性不同,那么A如果有异常,B怎么办,B如果有异常,A怎么办
- 总:事务的传播特性指的是不同方法的嵌套调用过程中,事务应该如何进行处理,是用同一个事务还是不同的事务,当出现异常的时候会回滚还是提交,两个方法之间的相关影响,在日常工作中,使用比较多的是required,Requires_new,nested
- 分:
- 先说事务的不同分类,可以分为三类:支持当前事务,不支持当前事务,嵌套事务
- 如果外层方法是required,内层方法是,required,requires_new,nested
- 如果外层方法是requires_new,内层方法是,required,requires_new,nested
- 如果外层方法是nested,内层方法是required,requires_new,nested
- 核心处理逻辑非常简单,判断内外方法是否是同一个事务:
是:异常统一在外层方法处理
不是:内层方法有可能影响到外层方法,但是外层方法是不会影响内层方法的(大致可以这么理解,但是有个别情况不同,nested)
REQUIRED和NESTED回滚的区别
在回答两种方式区别的时候,最大的问题在于保存点的设置,很多会认为内部设置REQURED和NESTED效果是一样的,其实在外层方法对内层方法的异常情况在进行楠获的时候区别都不同
使用REQURED的时候,会报Transcionrolled back becauseit hasbeen marked as allackonly信息,因为内部异常了,设置了回滚标记,外部捕获之后,要进行事务的提交意味着要回滚,所以会报异常
NESTED不会发证这种情况,因为在回滚的时候把回滚标记清除了,外部捕获异常后去提交,没发现回滚标记.就可以正常提交了。
REQUIRED_NEW和REQUIRED区别
这两种方式产生的效果是一样的
但是REQUREDJ NEw会有新的连接生成,而NESTED使用的是当前事务的连接,
而且NESTED还可以回滚到保存点、REQURED NEW每次都是一个新的务的回滚
但NESTED其实是一个事务,外层事务可以控制内层事务的回滚,内层就算没有异常.外层出现异常,也可以全部回滚。
springMVC
什么是SpringMVC
- SpringMVC是一种基于 Java 的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring框架的一个模块。
- 它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。
SpringMvc的执行流程
!
- 用户点击某个请求路径,发起一个request请求,此请求会被前端控制器处理。
- 前端控制器请求处理器映射器去查找Handler。可以依据注解或者XML配置去查找。
- 处理器映射器根据配置找到相应的Handler(可能包含若干个Interceptor拦截器),返回给前端控制器。
- 前端控制器请求处理器适配器去执行相应的Handler处理器(常称为Controller)。
- 处理器适配器执行Handler处理器。
- Handler处理器执行完毕之后会返回给处理器适配器一个ModelAndView对象(SpringMVC底层对象,包括Model数据模型和View视图信息)。
- 处理器适配器接收到Handler处理器返回的ModelAndView后,将其返回给前端控制器。
- 前端控制器接收到ModelAndView后,会请求视图解析器(ViewResolver)对视图进行解析。
- 视图解析器根据View信息匹配到相应的视图结果,反馈给前端控制器。
- 前端控制器收到View具体视图后,进行视图渲染,将Model中的模型数据填充到View视图中的request域,生成最终的视图(View)。
- 前端控制器向用户返回请求结果。
Spring MVC的主要组件?
前端控制器(DispatcherServlet):其作用是接收用户请求,然后给用户反馈结果。它的作用相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。
处理器映射器(HandlerMapping):其作用是根据请求的URL路径,通过注解或者XML配置,寻找匹配的处理器信息。
处理器适配器(HandlerAdapter):其作用是根据映射器处理器找到的处理器信息,按照特定规则执行相关的处理器(Handler)。
处理器(Hander):其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至ModelAndView对象中。
视图解析器(ViewResolver):其作用是进行解析操作,通过ModelAndView对象中的View信息将逻辑视图名解析成真正的视图View(如通过一个JSP路径返回一个真正的JSP页面)。
视图(JSP,FreeMarker等):View是一个接口,实现类支持不同的View类型(JSP、FreeMarker、Excel等)
设计模式
说一下你了解的几种设计模式
总体来说设计模式
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为型模式,共十一种:策略模式、模板方法模式、观察者模、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式
关系模型
is-a:继承关系 has-a:从属关系 like-a:组合关系
单例模式的特点
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式有那些类型
懒汉模式
- 普通懒汉
- 在getInstance方法上加同步
- 双重检查锁定
- 静态内部类、
饿汉模式
工厂模式
工厂模式的主要类型
工厂模式主要有三种类型
1、简单工厂
2、工厂方法
3、抽象工厂
Springboot
Spring Boott比Spring做了哪些改进?
-
Spring Boot可以建立独立的Spring应用程序;
-
内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说可以直接跑起来,用不着再做部署工作了;
-
无需再像Spring那样搞一堆繁琐的xml文件的配置;
-
可以自动配置Spring.SpringBoot将原有的XML配置改为Java配置,将bean注入改为使用注解注入的方式(@Autowire),并将多个xml,properties配置浓缩在一个appliaction.yml配置文件中。
-
提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
-
整合常用依赖(开发库,例如spring-webmvc、 jackson-json、validation-api和tomcat等),提供的POM可以简化Maven的配置当我们引入核心依赖时,SpringBoot会自引入其他依赖。
spring boot 核心配置文件是什么?
1.SpringBoot的核心配置文件有哪些?
SpringBoot的核心配置文件有application和bootstarp配置文件。
2.他们的区别是什么?
application文件主要用于Springboot自动化配置文件。
bootstarp文件主要有以下几种用途:
- 使用Spring Cloud Config注册中心时 需要在bootStarp配置文件中添加链接到配置中心的配置属性来加载外部配置中心的配置信息。
- 一些固定的不能被覆盖的属性
- 一些加密/解密的场景
Spring Boot 自动装配机制的原理?
自动装配,就是自动去把第三方组件的Bean装载到IOC容器里面,不需要开发人员再去写Bean相关的一个配置,在SpringBoot应用里,只需要在启动类上加上@SpringBootApplication注解就可以去实现自动装配,SpringBootApplication注解是一个复合注解,真正去实现自动装配的是EnableAutoConfiguration注解
自动装配的实现的主要依靠三个核心的关键技术
- 引入starter,启动依赖组件的时,这个组件里面必须要包含一个Configuration配置类,而在这个配置类里面,我们需要通过Bean这个注解去声明,需要装配到IOC容器里面的Bean对象
- 这个配置类是放在第三方的Jar包里面,通过SpringBoot中约定优于配置的理念去把这个配置类的全路径放在:Classpath;/META-INF/spring.factories文件里面,这样SpringBoot就可以知道第三方Jar里面配置类的位置,这个步骤,主要是用到了spring里面的SpringFactoryLoader来完成的
- SpringBoot拿到所有第三方Jar包里面声明的配置类后,再通过Spring提供的ImportSelector这样一个接口来实现对这些配置类的动态加载,从而去完成自动装配
在我看来呢,SpringBoot是约定优于配置这一理念下的一个产物,所以在很多的地方都会看到这一类的思想,它出现了让开发人员可以更加聚焦的在业务代码的编写上,而不需要去关心和业务无关的配置,其实啊,这种装配的思想在spring framework3.x版本里面的Enable注解就已经有了实现的一个雏形,Enable注解是一个模块驱动的意思,也就是说我们只需要增加Enable注解,就能自动打开某个功能,而不需要针对这个功能去做Bean的配置,Enable注解具体的底层呢,也是去帮我们自动去完成这样一个模块相关Bean的注入的
SpringCloud
什么是微服务架构
微服务不只是springcloud框架
一个项目的部署从单体式架构到SOA架构到微服务架构
对于一个项目,有很多的服务,微服务中就会产生一个服务集群,集群之间产生很多的关联
对于这些服务需要管理,就有一个注册中心来同一注册管理
对于各式各样的服务就有文件的配置,对服务集群的配置文件统一管理的配置中心
对于服务的外部访问,服务网关就来处理多种外部请求,请求路由,负载均衡
服务集群需要对于数据进行数据的保存,连接数据库,数据库也需要集群分化
对于数据库的请求过多会导致居多问题,分布式缓存就能缓解数据的请求处理
然后对于数据库的数据查询,有时候也会产生很多性能缺失,分布式搜索应运而生
服务与服务之间的请求也会产生访问不对的,消息队列就来管理这些服务间的请求
还有分布式日志管理记录数据服务之间的请求,系统监控/链路追踪也是对访问请求的追踪监控等
然后使用Jenkins对你的微服务项目进行统一编译,docker进行打包,产生镜像
在通过Kubernets,Rancher进行项目部署,这一套流程下来就是微服务
什么是服务网格?
服务网格就是Service Mesh,它是专门用来去处理服务端通信的一个基础设施层,它主要功能是去处理服务之间的一个通信并且负责实现请求的可靠性传递,Service Mesh我们通常可以把它称为第三代微服务架构,也意味着它是在原来的微服务架构的基础上去做的升级,为了更好的就说明Service Mesh那我就不得不说一下关于微服务架构这一块的知识
首先当我们去把一个电商系统以微服务架构的方式去进行拆分之后,就会包含Web Server,Payment,inventory等等,这些微服务应用呢,全部被部署到Docker容器,由于每个服务的业务逻辑是独立的,比如说像支付服务它会负责支付的业务逻辑,订单服务么去实现订单的处理逻辑,Web Server呢,去实现客户端请求的响应等等,所以服务之间必须要去相互通信才能够去实现功能的一个完整性,比如用户把一个商品加入购物车,那么请求会进入到Web Server,然后转发到购物车服务去进行处理并且最终存入到数据库,在这个过程中啊,每个服务之间必须要知道对方的通信地址并且当有新的节点加入进来的时候,还需要去对这些通信地址进行动态的维护,所以在第一代的微服务架构里面,每一个微服务除了要实现自己的业务逻辑以外,还需要去解决上下游的寻址和通信,以及像容错等一些问题,于是就有了第二代微服务架构
第二代微服务架构,它引入了服务注册中心来去实现服务之间的一个寻址,并且服务之间的容错机制,负载均衡也逐步形成了独立的服务框架,比如主流的SpringCloud或者Spring Cloud Alibaba,在第二代微服务架构里,负责业务开发不仅仅需要关注业务的逻辑,还需要去花大量精力去处理微服务中的一些基础性的配置工作,虽然SpringCloud已经尽可能的去完成这些事情,但是对于开发人员来说,学习SpringCloud以及针对SpringCloud的配置和维护仍然存在比较大的一个挑战,另外也增加了整个微服务架构的一个复杂性。
实际上,在我看来微服务中的所有的这些服务,注册也好,容错也好,安全工作等等也好,都是为了去保证服务之间通信的一个可靠性,于是呢就有了第三代微幅架构
原本的微服务框架里面的微服务的基础能力,被进一步地从一个SDK演进到了一个独立的代理进程SideCar,SideCar的主要职责是负责处理各个微服务之间的通信,承载了原本第二代微服务架构中的服务发现,调用容错,服务治理等这样一些功能,实现微服务的能力和业务逻辑实现彻底的解耦,之所以我们称Service Mesh是服务网格,是因为在大规模的服务架构中,每一个服务的通信都是由SideCar来代理的,各服务之间的通信拓扑呢,看起来就像一个网格形状,SideCar的开源框架很多,有很多其中具有典型代表性的叫Istio,它是Google开源的一个这样的Service Mesh框架
什么是CAP原则
CAP原则又称CAP定理,指的是在一个分布式系统中
一致性(Consistency)
可用性(Availability)
分区容错性(Partition tolerance)
服务之间如何实现远程调用,说说你的办法?(不使用Feign)
利用restTemplate,在启动文件上配置RestTemplate来调用http请求,在服务层通过RestTemplate调用需调服务路径,加上返回数据的Class类,实现远程调用
Eureka中的心跳监测机制是什么?
30秒一次,客服端往服务器发送一次心跳,证明服务还存在
说一说你了解的负载均衡路由算法
- 随机算法
- 加权随机算法
- 轮训算法
- 加权轮询算法
- 取模算法
- 一致性Hash算法
- 以ip地址进行分配,数值ipv4 及0->2^32-1上
- 预分配哈希槽算法
- 哈希槽——>均衡的分配
RPC远程调用
负载均衡的配置在项目中是如何实现的
1.在Application文件中添加Bean的自定义IRUle类方法
2.在配置文件中选择负载均衡规则
介绍一下Ribbon的加载方式
默认是懒加载,可以在配置文件中选择开启饥饿加载(可以选择单个还是多个服务器采用饥饿加载)
Nacos服务的分级存储模型
nacos默认服务地址是8848
对于非临时实例,和Eureka一样的心跳监测
对于临时实例,nacos会主动询问服务是否存在
对于配置文件会先寻找服务名加环境的配置,然后是服务名不加环境的配置,最后才是本地配置(有统一配置管理的情况下)
基于业务划分 服务-集群-实例 优先选择本地集群
基于概念划分 环境隔离 namespace 不同环境下不可见
可以通过两种方式实现配置管理热更新
- 在Value 结合@RefreshScope刷新
- 通过@ConfigurationProperties注入
网关的作用
- 身份认证权限校验
- 服务路由,负载均衡
- 请求限流
zuul阻塞式 ,gateway响应式
网关的跨域请求怎么处理
跨域有域名不同和域名相同情况下的端口不同两种情况
解决方案:CORS 跨域资源共享
说一说你知道的在高并发的场景之下,系统保护机制
先讲述在高并发场景下,一个系统没有被保护的情况
例如:下游服务宕机导致上游服务请求量增加导致上游服务不可用或者宕机
-
超时
当服务a调用服务b的时候,给服务a的调用线程设置一个超时时间,出现请求异常,超出超时时间,就需要赶紧释放掉服务按对应的线程资源
-
限流
“雪崩问题”一般发生在高并发场景下,所以可以在服务中增加限流控制,一旦超过阈值就不在处理新的请求
-
断路器
断路器有三种状态,开启,关闭和半开放
-
仓壁模式
针对不同的目标设定不同的线程池
分布式ID生成方式
- uuid
- 数据库递增
- 随机数
- 随机字符串
- 基于雪花算法(Snowflake)模式
三种实现分布式锁的方式
基于数据库实现分布式锁;
基于缓存(Redis等)实现分布式锁;
基于Zookeeper实现分布式锁;
说一说你对分布式锁的理解已经实现
分布式锁是一种跨进程,跨机器节点的一种锁
他可以用来来保证多个机器节点对共享资源的排它机制
我觉得他对于线程锁本质上是一样的,线程锁的生命周期是单进程多线程,分布式锁的生命周期是多进程多机器节点
在本质上都需要满足锁的几个基本重要特性:
- 排他性(只能有一个节点访问共享资源)
- 可重入性(允许已经获取到锁的节点在没有释放锁之前重新获取锁)
- 锁的获取、释放
- 锁的失效机制
所以只要满足这些锁的特性的技术组件都能满足分布式锁
1.关系型数据库:
他可以使用到唯一的约束,来实现锁的排他性,如果要针对某个来进行加锁,就可以设置一个包含方法名称的一个字段并且把方法名称设置成唯一的约束
抢占锁的逻辑就变成了往表里去插入一条数据,如果已经有其它的线程获得了某个方法的锁,那么这个时候再去插入这个数据一定会失败,这样就实现了锁的互斥性
虽然这个锁的实现方法看起来很简单,但是实现比较完整的分布式锁还是需要到考虑到重入性,锁的失效机制,没有抢占掉锁的线程阻塞等,都会比较麻烦
2.Redis
通过实现SETNX命令实现锁的排他性,当key不存在就返回1,存在就返回0
然后还可以使用expire命令去设置锁的失效时间,从而避免死锁的问题
当然有可能存在锁过期了,但是业务逻辑还没有执行完成 怎么办呢
所以这种情况下,我们可以写个定时任务对指定的key去进行续期,Redision这个开源组件提供了一个分数锁的封装实现,并且内置了一个叫Watch Dog的机制来对Key做续期
所以我认为Redis这种锁的设计已经能够解决百分之99的问题了
分布式锁应该是一个Cp模型,Redis是一个AP模型,所以在集群架构下,由于数据的一致性问题导致极端情况下出现多线程或者多进程抢占锁的情况很难避免
3.基于CP模型的分布式锁ZooKeeper/etcd
在数据一致性方面,Zookeeper用到了zab协议保证数据的一致性
etcd用到了raft算法保证数据的一致性
在锁的互斥方面,Zookeeper基于有序节点再结合Watch机制来实现互斥和唤醒
etcd可以基于Prefix机制和Wach机制
主从切换导致Key失效的问题?
Redis官方提供了一个叫RedLock的解决办法,实现上会相对复杂
Dubbo
简单介绍一下Dubbo
阿里巴巴高性能轻量级java RPC框架
致力于提供高性能,透明化的RPC远程调用调用方案,以及SOA服务治理
Linux
Docker
什么是docker,简单的介绍一下docker
Docker是一个快速交付应用,运行应用的技术
1.可以将程序及其依赖,运行环境一起打包为一个镜像,迁移到任意操作系统
2.运行时利用沙盒机制形成隔离容器,各个应用互不干扰
3.启动移除都可以通过一行命令完成,方便快捷
Git
什么是GIT
分布式多人协作版本控制系统,是一种文件管理系统,被它管理的文件内容发生任何变化它都能记录下来,我们可以根据这个记录对一个文件溯源与特定的场景操作。版本控制系统不仅可以应用于软件源代码的文本文件,而且可以对任何类型的文件进行版本控制。
GIT的工作模式
Git分为四块区域:工作区,暂存区,本地历史仓库和远程仓库
工作区可以将数据add提交到暂存区
暂存区可以commit提交到本地历史仓库
本地仓库可以push上传到远程仓库
远程仓库fetch/clone到本地仓库
本地仓库reset到暂存区
暂存区checkout到工作区
远程仓库也可以pull到工作区
Git常用命令
Git add . 提交当前目录未提交的文件暂存区
Git add * 提交所有位置未提交的文件到暂存区
Git commit -m 注释内容
Git status 查看工作区是否有未提交
Git rm file 删除文件
Git log 查看提交的文件版本信息
git reflog 查看所有的版本信息
git reset head ^ 回退到上一个版本
Git pull remoteprojectname 更新代码库
Git push remoteprojectname master 将本地提交到远程库
分支使用
查看分支:git branch
创建分支:git branch name
切换分支:git checkout name
创建+切换分支:git checkout –b name
合并某分支到当前分支:git merge name
删除分支:git branch –d name
Maven
Maven 是什么?
Maven 主要服务于基于 Java 平台的项目构建、依赖管理和项目信息管理。
Maven的工程类型有哪些?
- POM工程
- JAR工程
- WAR工程
Maven常用命令有哪些?
- install
本地安装, 包含编译,打包,安装到本地仓库
编译 - javac
打包 - jar, 将java代码打包为jar文件
安装到本地仓库 - 将打包的jar文件,保存到本地仓库目录中。 - clean
清除已编译信息。
删除工程中的target目录。 - compile
只编译。 javac命令 - deploy
部署。 常见于结合私服使用的命令。
相当于是install+上传jar到私服。
包含编译,打包,安装到本地仓库,上传到私服仓库。 - package
打包。 包含编译,打包两个功能。
Maven 生命周期的操作
- clean :清理自动生成的文件,也就是 target 目录。
- validate :验证 Maven 描述文件是否有效。
- compile :编译 java 代码。
- test :运行测试代码。
- package :项目打成 jar、war 包等。
- verify :验证构件包是否有效。
- install :将构件包安装到本地仓库。
- deploy :将构件包部署到远程仓库。
- site :生成项目站点。
FreeMarker
什么是freeMarker?
FreeMarker是一个用Java语言编写的模板引擎。它基于模板来生成文本输出
在我的理解freemarker就是一种工具,它能把jsp页面转换成静态页面,为用户的访问节省时间,同样减少服务器的压力。
计算机网络
从浏览器地址栏输入 url 到显示主页的过程
- DNS 解析,查找真正的 ip 地址
- 与服务器建立 TCP 连接
- 发送 HTTP 请求
- 服务器处理请求并返回 HTTP 报文
- 浏览器解析渲染页面
- 连接结束
如何理解 HTTP 协议是无状态的
每次 HTTP 请求都是独立的,无相关的,默认不需要保存上下文信息的
谈谈你对网络四元组的理解
四元组就是在TCP协议里面去确定一个客户端连接的组成要素,它包括
-
源IP地址
-
目标IP地址
-
源端口号
源端口号每次建立的时候是系统自动分配的
-
目标端口号
当一个客户端和服务端去建立一个TCP连接的时候,通过原IP地址目标IP地址,源端口号和目标端口号来确定一个唯一的TCO连接,服务器的IP和端口是不变的,只要客户端的IP和端口彼此不同就OK
讲一下 HTTP 与 HTTPS 的区别
- 数据是否加密: Http 是明文传输,HTTPS 是密文
- 默认端口: Http 默认端口是 80,Https 默认端口是 443
- 资源消耗:和 HTTP 通信相比,Https 通信会消耗更多的 CPU 和内存资源,因为需要加解密处理;
- 安全性: http 不安全,https 比较安全。
TCP 和 UDP 的区别
- TCP 面向连接((如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不需要建立连接。
- TCP 要求安全性,提供可靠的服务,通过 TCP 连接传送的数据,不丢失、不重复、安全可靠。而 UDP 尽最大努力交付,即不保证可靠交付。
- TCP 是点对点连接的,UDP 一对一,一对多,多对多都可以
- TCP 传输效率相对较低,而 UDP 传输效率高,它适用于对高速传输和实时性有较高的通信或广播通信。
- TCP 适合用于网页,邮件等;UDP 适合用于视频,语音广播等
- TCP 面向字节流,UDP 面向报文
TCP 协议如何保证可靠传输
- 应⽤数据被分割成 TCP 认为最适合发送的数据块。
- TCP 给发送的每⼀个包进⾏编号,接收⽅对数据包进⾏排序,把有序数据传送给应⽤层。
- 校验和: TCP 将保持它⾸部和数据的检验和。这是⼀个端到端的检验和,⽬的是检测数据
在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报⽂段和不确认收
到此报⽂段。 - TCP 的接收端会丢弃重复的数据。
- 流量控制: TCP 连接的每⼀⽅都有固定⼤⼩的缓冲空间,TCP的接收端只允许发送端发送
接收端缓冲区能接纳的数据。当接收⽅来不及处理发送⽅的数据,能提示发送⽅降低发送的
速率,防⽌包丢失。TCP 使⽤的流量控制协议是可变⼤⼩的滑动窗⼝协议。 (TCP 利⽤滑
动窗⼝实现流量控制) - 拥塞控制: 当⽹络拥塞时,减少数据的发送。
- ARQ协议: 也是为了实现可靠传输的,它的基本原理就是每发完⼀个分组就停⽌发送,等
待对⽅确认。在收到确认后再发下⼀个分组。 - 超时重传: 当 TCP 发出⼀个段后,它启动⼀个定时器,等待⽬的端确认收到这个报⽂段。
如果不能及时收到⼀个确认,将重发这个报⽂段。
TCP 报文首部有哪些字段,说说其作用
- 16 位端口号:源端口号,主机该报文段是来自哪里;目标端口号,要传给哪个上层协议或应用程序
- 32 位序号:一次 TCP 通信(从 TCP 连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。
- 32 位确认号:用作对另一方发送的 tcp 报文段的响应。其值是收到的 TCP 报文段的序号值加 1。
- 4 位头部长度:表示 tcp 头部有多少个 32bit 字(4 字节)。因为 4 位最大能标识 15,所以 TCP 头部最长是 60 字节。
- 6 位标志位:URG (紧急指针是否有效),ACk(表示确认号是否有效),PSH(缓冲区尚未填满),RST(表示要求对方重新建立连接),SYN(建立连接消息标志接),FIN(表示告知对方本端要关闭连接了)
- 16 位窗口大小:是 TCP 流量控制的一个手段。这里说的窗口,指的是接收通告窗口。它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
- 16 位校验和:由发送端填充,接收端对 TCP 报文段执行 CRC 算法以检验 TCP 报文段在传输过程中是否损坏。注意,这个校验不仅包括 TCP 头部,也包括数据部分。这也是 TCP 可靠传输的一个重要保障。
- 16 位紧急指针:一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP 的紧急指针是发送端向接收端发送紧急数据的方法。
人事面
说一说你的优点
一个计划性很强
善于与人交谈(适应性很强)
我能从不同的角度看问题,不管面对多大的困难我都能完成我的工作
有责任感
说一说你的缺点
-
我有时候急于求成,一旦接手一个任务,总是想要尽快把它赶完,总觉得做完了一件事情心里才舒服。但是,欲速则不达,太追求效率,就会牺牲准确度。我现在总是提醒自己准确度第一位,效率第二位,这样会好得多。
-
首先我刚刚毕业,经验方面不足,不过我会积极完成工作,积累工作经验,其次,我有时候性子可能有点急点,对效率低的人缺乏点耐心,但我会在平时注意控制自己的语速和讲话,培养自己的耐心。
你和别人比,你觉得他们强的地方在哪里?(问觉得你别人强在哪同理)
每个人的优缺点不一,但是别人厉害的地方我都会学习,取长补短.毕竟每个人的优势不一样.
然后说自己的优点在哪
说一说你的期望薪资
我对工资没有硬性要求。我相信贵公司在处理我的问题上会友善合理。我注重的是找对工作机会,所以只要条件公平,我则不会计较太多
我受过系统的软件编程的训练,不需要进行大量的培训。而且我本人也对编程特别感兴趣。因此,我希望公司能根据我的情况和市场标准的水平,给我合理的薪水
如果你必须自己说出具体数目,请不要说一个宽泛的范围,那样你将只能得到最低限度的数字。最好给出一个具体的数字这样表明你已经对当今的人才市场作了调查,知道像自己这样学历的雇员有什么样的价值。
说一说你对加班,出差的看法
刚进公司,需要对环境、业务、代码短期内提高熟悉度,会选择适量加班,公司和项目需要加班时,会和大家一起拼命,这是保证项目进度和质量的自我要求。平时都能高效完成任务,一般不需要加班
最能概括你自己的三个词是什么?
乐观,有趣,有目标
你还有问题要问的
可以问公司的一些项目情况,晋升制度,员工职业规划,培训机制,或者面试个人表现
说一说你的家庭
企业面试时询问家庭问题不是非要知道求职者家庭的情况,探究隐私,企业不喜欢探究个人隐私,而是要了解家庭背景对求职者的塑造和影响。企业希望听到的重点也在于家庭对求职者的积极影响。企业最喜欢听到的是:
我很爱我的家庭!我的家庭一向很和睦,虽然我的父亲和母亲都是普通人,他们的行动无形中培养了我认真负责的态度和勤劳的精神。他们的一言一行也一直在教导我做人的道理。
你平时喜欢做什么
多说一些团队向的事情
玩一玩团队竞技的moba游戏,喜欢和朋友一起打打羽毛球,喜欢唱歌,也喜欢查看新闻同时了解前沿技术
你做过的哪件事最令自己感到骄傲(最有成就的事)?
说一说三下乡,或者当部长,虽然自己做的一些事很普通但是当自己完成之后,做到了负责,就有感觉到很有成就
你有没有坚持很久的一件事
坚持良好的作息生活,坚持自律学习,运动等
收到OFFER该问的5个问题
- 试用期工资和时间(正常来说合同是3年,试用期是3个月)
- 转正考核的方式,转正工资
- 工作时间以及加班计算(一周五天工作制,一天8小时)
- 培训晋升机制
- 福利情况(五险一金,餐补,其他补贴如电脑补贴)