面试准备知识点

只为个人记录

文章目录

https://blog.csdn.net/ThinkWon/article/details/103592572

面试题

序号内容链接地址
1Java基础知识面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390612
2Java集合容器面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588551
3Java异常面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390689
4并发编程面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104863992
5JVM面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390752
6Spring面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397516
7Spring MVC面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397427
8Spring Boot面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397299
9Spring Cloud面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397367
10MyBatis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/101292950
11Redis面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/103522351
12MySQL数据库面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104778621
13消息中间件MQ与RabbitMQ面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588612
14Dubbo面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104390006
15Linux面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104588679
16Tomcat面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397665
17ZooKeeper面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104397719
18Netty面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/104391081
19架构设计&分布式&数据结构与算法面试题(2020最新版)https://thinkwon.blog.csdn.net/article/details/105870730

1. 数据结构

常用数据结构java实现

https://www.cnblogs.com/javafirst0/p/10825309.html

基本概念和术语

  • 数据: 是客观事物的符号表示,能够输入到计算机中并能被计算机程序处理的符号的总称
  • 数据元素: 是数据的基本单位,用于完整地描述一个对象
  • 数据对象: 是性质相同的数据元素的集合,是数据的一个子集
  • 数据项: 是组成数据元素的,有独立含义的,不可分割的最小单位
  • 数据结构: 是相互之间存在的一种或者多种的特定关系的数据元素的集合,换句话说,数据结构是带结构的数据元素的集合,“结构”,就是指数据元素之间的关系。数据结构包括,逻辑结构和存储结构两个层次。

逻辑结构: 两个要素:数据元素和关系

四种基本逻辑结构:集合结构,线性结构,树结构,图结构

非线性结构:树,二叉树,有向图,无向图
线性结构:线性表(线性表,栈与队列,字符串,数组,广义表)

存储结构: 数据对象在计算机中的存储表示成为数据的存储结构,也称为物理结构

顺序存储结构:
顺序存储是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系
通常借助程序设计语言的 数组 来表示。

链式存储结构:
顺序存储结构要求所有元素依次存放在一片连续的存储空间内
链式存储结构,则不需要占据一整块存储空间
只需要给每个节点附加指针,用于存放后继元素的存储地址
通常借助于程序设计语言的 指针 来表示。

1.1 数组

定义: 数组是由类型相同的元素构成的有序集合,每个元素称为数组元素

特点: 结构中的元素本身可以是具有某种结构的数据,但属于统一数据类型

数组的顺序存储:

由于数组一般不做插入或删除操作,所以一般采用顺序存储。

对于二维数组来说,有两种存储方式

以行序为主的存储 ,以列序为主的存储

1.2 栈

栈和队列的定义和特点

栈:是限定仅在表尾进行插入或者删除的线性表,所以,对于栈来说,表尾端有特殊含义,称为栈顶,表头称为栈底,其最大的特点就是后进先出

火车的调度就是栈的形象化理解

1.3 队列

队列:是仅允许在表的一端插入,一端删除,前者那一端称为队尾,后者那一端称为队头,实际生活中,排队时最好的例子,其最大的特点是先进先出

生活中的排队,就是一个队列

1.4 链表

  • 基本特点:除第一个元素无直接前驱,最后一个元素无直接后继以外,其他每个基本元素都有一个前驱和后继
  • 线性表是最基本最常用的一种线性结构,也是其他数据结构的基础,尤其单链表
  • 定义:由n(n≥0)个数据特性相同的元素构成的有限序列称为线性表
  • 空表: 线性表中元素的个数n定义为线性表的长度,n=0时为空表
  • 顺序表: 线性表的顺序表示指的时用一组地址连续的存储单元依次存储线性表的数据元素,这种表示页称作表的顺序存储结构(随机存取)或顺序映像。
    顺序表特性:逻辑上相邻的数据元素,其物理地址次序也是相邻的
线性表的两种实现(java)
顺序表链表
空间性能顺序表的存储空间是静态分布的,需要一个固定的数组,总有部分数组元素要浪费链表的存储空间是动态分布,因此不会有空间被浪费。但由于链表需要额外的空间来为每个节点保存指针,因此也要牺牲一部分空间
时间性能顺序表中的元素的逻辑顺序和物理存储顺序保持一致,而且支持随机存取。因此顺序表在查找,读取时候效率很快链表采用链式结构来保存表内的元素,因此在插入、删除的时候效率比较高
1. 线性表本质上是一个充当容器的工具类,当程序有一组结构相同的数据元素需要保存的时候,就可以考虑使用线性表来保存。
2. Java中经常使用的线性表是list,Java中list接口就是代表线性表,线性表中常见的两种实现分别是ArrayList和LinkedList,其中LinkedList是一个双向链表,而ArrayList是动态数组来实现。
3. ArrayList实现原理是数组,有点在于遍历查找速度很快,但是对于插入和删除效率不高。
4. LinkedList的实现就是链表遍历和查找速度不高,但是插入和删除的效率很高。

1.5 树

基本术语:

  • 结点: 树中的一个独立单元,包含一个数据元素及若干指向子树的分支,如图5.1(b)中的A,B,C,D等(下面术语,均以图5.1(b)为例)
  • 结点的度: 结点拥有的子树数称为结点的度,例如,A的度为3,C的度为1,F的度为0
  • 树的度: 树的度是树内各结点度的最大值,5.1(b)所示的树的度为3
  • 叶子: 度为0的结点称为叶子或者终端结点。结点K,L,F,G,M,I.J都是树的叶子
  • 非终端结点: 度不为0的结点称为非终端结点或者分支结点,除根结点,非终端结点也称为内部结点
  • 双亲和孩子: 结点的子树的根称为该结点的孩子,相应的该结点称为孩子的双亲,例如:B的双亲为A,B的孩子由E和F
  • 兄弟: 同一个双清的孩子之间互称为兄弟,例如H,I,J互为兄弟
  • 祖先: 从根到该结点所经分支上的所有结点,例如:M的祖先为A,D,H
  • 子孙: 以某结点为根的子树中的任一结点都称为该结点的子孙,如B的子孙为E,K,L,F
  • 层次: 结点的层次从根结点定义起,根为第一层,根的孩子为第二层,树中任一结点的层次等于其双亲层次加1
  • 堂兄弟: 双亲在同一层的结点互为堂兄弟。例如:结点G与E,F,H,J,I为堂兄弟
  • 树的深度: 树中的结点的最大层次称为树的深度或高度,例子中的深度为4
  • 有序树和无序树: 如果将树中结点的各子树看成从左至右是有次序的(即不能互换),则称该树为有序树,否则称为无序树。在有序树中最左边的子树的根称为第一个孩子,最右边的称为最后一个孩子
  • 森林: 是m(m≥0)棵互不相交的树的集合,对于树中每个结点而言,其子树的集合即为森林

1.5.1 二叉树

树和二叉树的定义:

树: 是n ( n ≥ 0 )个结点的有限集,它或为空树(n=0),或为非空树,对于非空树 T:

  • 有且仅有一个称之为根的结点;
  • 除根节点以外的其余结点可分为m( m > 0 )个互不相交的有限集T 1 , T 2 , . . . , T m 其中每一个集合本身又是一棵树,并且称为根的子树

二叉树: 是n ( n ≥ 0 )个结点所构成的集合,它或为空树或为非空树,对于非空树T:

  • 有且仅有一个称之为根的结点;
  • 除根结点以外的其余结点分为两个互不相交的子集T 1 , T 2 ,分别称为T的左子树和右子树,且T 1 , T 2 本身又都是二叉树

1.5.2 完全二叉树

深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中的编号从1至n的结点一一对应时,称为完全二叉树

特点

  • 叶子结点只可能是在层次最大的两层出现;
  • 对于任一结点,若其右分支下的子孙的最大层次为 i,则其左分支下的子孙的最大层次必为 i 或者 i+1

1.5.3 平衡二叉树

红黑树(Red Black Tree)是一种自平衡二叉搜索树(二叉查找树),是一种特殊的二叉搜索树,在进行插入和删除时通过特定操作保持二叉树自身的平衡,从而获得较高的查找性能。

红黑树的平衡操作通过左旋、右旋和变色来实现,平衡的过程是比较复杂的,但通过平衡操作,可以获得更高效的性能。对二叉搜索树进行平衡后,最坏情况的运行时间得到优化,可以在O(logN)的时间复杂度内完成查找、插入和删除,N是二叉搜索树中的节点数。

一、二叉搜索树的性能分析

红黑树是一种特殊的二叉搜索树,所以本文先从二叉搜索树说起。

二叉搜索树是一种特殊的二叉树,具有如下特性:

  1. 如果二叉树的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。
  2. 如果二叉树的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。
  3. 如果独立地看,左子树、右子树也分别为二叉搜索树。

二叉搜索树的实现

向二叉搜索树中插入数据时,为了满足二叉搜索树的特性,会递归地比较插入节点的值与根节点的值,将数据插入正确的位置。

如在一棵空二叉搜索树中插入 [50, 77, 55, 29, 10, 30, 66, 80, 51, 18, 90] ,得到的二叉搜索树结构如下图:
在这里插入图片描述
从结构图可以看出,这棵二叉搜索树是平衡的,当在二叉搜索树中查找数据时,按照二分法查找的思想,从根节点开始,然后到子树中进行查找,如果没有查找到目标数据,每次都会往树的下一层进行查找,需要的最大查找次数等于树的深度。最坏的情况就是找到树的最深一层,所以在这棵树中查找的最坏情况是查找4次。

还是上面例子中的数据,假设比根节点50大的数据是升序排列的,如 [50, 51, 55, 66, 77, 80, 90, 29, 10, 30, 18] ,比根节点50小的数据顺序不变,将这些数据插入到二叉搜索树中,得到的二叉搜索树结构如下图:
在这里插入图片描述
很明显,这棵二叉搜索树是不平衡的。在这棵树中查找数据的最坏情况需要查找7次,查找次数多的原因就是树的不平衡,右子树一直在往深度上延伸。如果把根节点和右子树拿出来,结构如下图:
在这里插入图片描述
根据结构图,这是一棵右斜树。它虽然是一棵二叉树,但它更像是一个链表,正在向链表“退化”。链表的时间复杂度是O(N),平衡二叉树的时间复杂度是O(logN),当N很大的时候,O(N)与O(logN)的性能差距是很大的。

可见,二叉搜索树的插入顺序会影响到树的结构,从而影响性能。对于上面的例子,总共只有11个节点,出现7个数据顺序排列的可能性不大,但在实际的应用场景中,节点可能是110个,11000个,11000000个,甚至更多。在数据量增大的时候,这些数据中出现一段或多段数据顺序排列的可能性是很大的,这就会造成二叉搜索树极度不平衡,向链表退化,性能大大降低。

所以,保持二叉搜索树的平衡,对性能的保证有至关重要的作用。由于数据是动态变化的,会动态地增加或减少,不可能在构造二叉搜索树前控制数据的排列顺序。要保持二叉搜索树的平衡,就要在每增加一个节点或每减少一个节点时都保持平衡,即让二叉搜索树一直保持平衡,这样二叉搜索树的性能就不可能向链表退化。

当二叉搜索树中的节点数量发生改变时,使用一些策略来保持平衡,红黑树就是这样一种二叉树。

1.5.5 二叉查找树(BST)

1.5.6 红黑树

红黑树是一种自平衡二叉搜索树,每个节点都有颜色,颜色为红色或黑色,红黑树由此得名。除了满足二叉搜索树的特性以外,红黑树还具有如下特性(定义):

  1. 节点是红色或黑色。
  2. 根节点是黑色。
  3. 所有叶子节点都是黑色的空节点。(叶子节点是NIL节点或NULL节点)
  4. 每个红色节点的两个子节点都是黑色节点。(从每个叶子节点到根的所有路径上不能有两个连续的红色节点)
  5. 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点

1.5.7 B,B+,B*树

https://blog.csdn.net/chai471793/article/details/99563704

2. 数据库

2.1 MySQL

面经

1、讲讲mysql有几个事务隔离级别?

读未提交,读已提交,可重复读,串行化四个!默认是可重复读

数据库之【事务特性】和【隔离级别】和【传播行为】

2、为什么mysql选可重复读作为默认的隔离级别?

主从复制,是基于什么复制的?

是基于binlog复制的!这里不想去搬binlog的概念了,就简单理解为binlog是一个记录数据库更改的文件吧~

binlog有几种格式?

OK,三种,分别是

  • statement:记录的是修改SQL语句
  • row:记录的是每行实际数据的变更
  • mixed:statement和row模式的混合
    那Mysql在5.0这个版本以前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!

你们项目中选了哪个隔离级别?为什么?

3、sql优化

https://www.cnblogs.com/sharpest/p/10390035.html

4、最左匹配原则

在Mysql建立多列索引(联合索引)有最左前缀的原则,即最左优先。

如果我们建立了一个2列的联合索引(col1,col2),实际上已经建立了两个联合索引(col1)、(col1,col2);
如果有一个3列索引(col1,col2,col3),实际上已经建立了三个联合索引(col1)、(col1,col2)、(col1,col2,col3)。

解释
1、b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+树是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道第一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。

2、比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。(这种情况无法用到联合索引)

mysql里创建联合索引的意义

一个顶三个
建了一个(a,b,c)的复合索引,那么实际等于建了(a),(a,b),(a,b,c)三个索引,因为每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,这可是不小的开销!

覆盖索引

同样的有复合索引(a,b,c),如果有如下的sql: select a,b,c from table where a=1 and b = 1。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作。减少io操作,特别的随机io其实是dba主要的优化策略。所以,在真正的实际应用中,覆盖索引是主要的提升性能的优化手段之一

索引列越多,通过索引筛选出的数据越少

有1000W条数据的表,有如下sql:select * from table where a = 1 and b =2 and c = 3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W*10%=100w 条数据,然后再回表从100w条数据中找到符合b=2 and c= 3的数据,然后再排序,再分页;如果是复合索引,通过索引筛选出1000w *10% *10% *10%=1w,然后再排序、分页,哪个更高效,一眼便知

创建联合索引时列的选择原则

经常用的列优先(最左匹配原则)
离散度高的列优先(离散度高原则)
宽度小的列优先(最少空间原则)
列的离散性计算:count(distinct col)/ count(col)

例如:
id列一共9列都不重复 9/9 = 1
性别列一共9列只有(男或者女)两列 2/9 约等于0.2
离散性越高选择性越大

5、explain中type也有助于sql的分析 https://blog.csdn.net/dennis211/article/details/78170079

6、在进行数据库查询时会造成索引失效的几种情况

1、字符串不使用单引号
2、范围查询时,右边的列不使用索引
3、违反最左前缀原则
4、在索引列上进行运算操作
5、用or连接的字段,前面的字段建立索引,后面的没有建立索引,这是前面的索引也会不起作用
6、模糊匹配时当使用的%在匹配内容之前
7、MySQL数据库会自动的对比走索引和不走索引的查询速度,当走索引比走全表查询慢的时候,数据库就会不走索引
8、is null 和is not null 有时会导致索引失效
9、not in

7、分库分表?分表键如何选择?
https://baijiahao.baidu.com/s?id=1622441635115622194&wfr=spider&for=pc

8、谈谈你对Mysql数据库读写分离的了解,并且有哪些注意事项?
https://juejin.cn/post/6844903828588855309

2.1.1 MySQL语句分类

2.1.1.1 数据定义语言DDL(Data Definition Language)

CREATE,DROP,ALTER

主要为创建、修改、删除数据库的逻辑结构,其中包括表结构,视图和索引等。

创建、删除数据库:CREATE DATABASE; DROP DATABASE

创建、修改、重命名、删除表:CREATE TABLE; ALTER TABLE; RENAME TABLE; DROP TABLE;

创建和删除索引:CREATE INDEX; DROP INDEX

2.1.1.2 数据查询语言DQL(Data Query Language)

SELECT

用于数据库中数据的检索查询。各种简单查询,连接查询等都属于DQL。

2.1.1.3 数据操纵语言DML(Data Manipulation Language)

INSERT,UPDATE,DELETE

主要用于数据库中数据的修改,包括添加、删除、修改等

插入数据到一个表中:INSERT语句

更新表中已有的数据:UPDATE语句

删除表中的数据:DELETE语句

2.1.1.4 数据控制语言DCL(Data Control Language)

GRANT,REVOKE,COMMIT,ROLLBACK

主要为数据库访问权限控制,给用户授予访问权限:GRANT语句,取消授权:REVOKE

2.1.1.5 事务控制语言TCL(Transaction Control Language)

BEGIN,SAVEPOINT xxx,ROLLBACK,ROLLBACK TO xxx,COMMIT

用于提交事务和回滚事务,维护数据的一致性

2.1.2 语句的唯一性约束

在user表中有username值为:Buffett的一条记录,且该表规定username不能为空,主键自增,详细的sql语句为:

DROP TABLE IF EXISTS `user`;
 CREATE TABLE `user`  (
   `user_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户id',
   `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户名',
   `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '密码',
   `mobile_phone_number` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '手机号码',
   `email` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '邮箱',
   `delete_state` tinyint(1) UNSIGNED DEFAULT 0 COMMENT '用户状态,1表示删除,0表示未删除',
   `create_time` datetime(0) DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
   `update_time` datetime(0) DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
   PRIMARY KEY (`user_id`) USING BTREE,
   UNIQUE INDEX `uk_username`(`username`) USING BTREE COMMENT '用户名唯一'
 ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
insert into `user`  ( username, PASSWORD, mobile_phone_number, email )  VALUES ( 8, 'Buffett', '123456', '13800000077', 'Buffett@163.com', 0, CURRENT_TIMESTAMP, NULL );
> 1062 - Duplicate entry '8' for key 'user.PRIMARY'
> 时间: 0.001s

简单总结:重复则报错,不重复则插入。

insert ignore into `user`  ( username, PASSWORD, mobile_phone_number, email )  VALUES  ( 'Buffett', '123456', '13800000088', 'Buffett@163.com' )
> Affected rows: 0
> 时间: 0.001s

简单总结:重复则忽略(只以警告形式返回,不执行此SQL语句 ),不重复则插入。

insert into `user` 
( username, PASSWORD, mobile_phone_number, email ) 
VALUES
( 'Buffett', '123456', '13800000088', 'Buffett@163.com' ) 
on duplicate key update UPDATE mobile_phone_number = '13800000088';
> Affected rows: 2
> 时间: 0.022s

简单总结:重复则更新指定字段,不重复则插入

1、尽量不对存在多个唯一键或主键的table使用该语句
2、在有可能有并发事务执行的insert 的内容一样情况下不使用该语句

另外,影响行数为2?

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values
官方明确说明了,插入影响1行,更新影响2行,0的话就是存在且更新前后值一样。这是为了区分到底是插入了还是更新了,返回1表示插入成功,2表示更新成功。利用这个特性可以在后续的业务开发中使用
或者
修改业务逻辑,将INSERT ... ON DUPLICATE KEY UPDATE ...语句拆开,先去查询,然后去更新,这样就可以保证主键不会不受控制的增大,但增加了复杂性,原来的一次请求可能变为两次,先查询有没有,然后去更新。
主键自增的特性和上面影响行数为2的原因:https://segmentfault.com/a/1190000017268633

REPLACE INTO `user` ( username, PASSWORD, mobile_phone_number, email )
VALUES
	( 'Buffett', '123456', '13800000099', 'Buffett@163.com' )
> Affected rows: 2
> 时间: 0.019s

简单总结:重复则先删除再插入新记录,不重复则插入,如果没有重复性问题,则执行插入操作,效果和insert into是一样的。

  • 如果出现重复异常,希望捕获异常,则使用insert into
    如果出现重复异常,希望保存旧纪录,忽略新纪录,则使用insert ignore into
    如果出现重复异常,希望更新指定字段,则使用insert into … on duplicate key update
    如果出现重复异常,希望删除旧记录,插入新记录,则使用replace into

2.1.3 datagirp设置update_time自动更新
在这里插入图片描述

2.1.4 复制表的三种方式

2.1.4.1 复制表结构及其数据

下面这个语句会拷贝数据到新表中。

注意:这个语句其实只是把select语句的结果建一个表,所以新表不会有主键,索引。

create table table_name_new as (select * from table_name_old);

2.1.4.2 只复制表结构

create table table_name_new as select * from table_name_old where 1=2;

或者

create table table_name_new like table_name_old;

注意:前一种方式是不会复制主键类型,索引的,而后一种方式是把旧表的所有字段类型都复制到新表。

2.1.4.3 只复制表数据

如果两个表结构一样

insert into table_name_new select * from table_name_old;

如果两个表结构不一样

insert into table_name_new(column1,column2…) select column1,column2… from table_name_old;

2.1.5 MySQL删除表的三种方式

drop 是直接删除表信息,速度最快,但是无法找回数据

drop table user;

truncate 是删除表数据,不删除表的结构,速度排第二,但不能与where一起使用

truncate table user;

delete 是删除表中的数据,不删除表结构,速度最慢,但可以与where连用,可以删除指定的行

delete from user; 删除所有记录

delete from user where user_id = 1;

总结

  • 语句类型:delete语句是数据库操作语言(DML),truncate,drop是数据库定义语言(DDL);
  • 效率:一般来说 drop > truncate> delete;
  • 是否删除表结构:truncate和delete 只删除数据不删除表结构,truncate 删除后将重建索引(新插入数据后id从0开始记起),而 delete不会删除索引 (新插入的数据将在删除数据的索引后继续增加),drop语句将删除表的结构包括依赖的约束,触发器,索引等;
  • 安全性:drop和truncate删除时不记录MySQL日志,不能回滚,delete删除会记录MySQL日志,可以回滚;
  • 返回值:delete 操作后返回删除的记录数,而 truncate 返回的是0或者-1(成功则返回0,失败返回-1);

2.1.6 count(字段) 和count(主键 id) 和count(1)和count(*)的区别

首先要弄清楚 count() 的语义。count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加。最后返回累计值。

所以,count(*)、count(1)和count(主键 id) 都表示返回满足条件的结果集的总行数;而 count(字段),则表示返回满足条件的数据行里面,参数“字段”不为 NULL 的总个数。

性能结论:

count(可空字段) < count(非空字段) = count(主键 id) < count(1) ≈ count(*) 【基于 InnoDB 引擎 】在这里插入图片描述
1、事务
1、1 基本概念
1、2 人人都知道事务的四大特性:ACID
1、3 但他们的实现原理是什么?
2、日志
2、1 重做日志(redo log)
2、2回滚日志(undo log)
2、3 二进制日志(binlog)
2、4 错误日志(errorlog)
2、5慢查询日志(slow query log)
2、6一般查询日志(general log)
2、7中继日志(relay log)
3、锁
3、1MySQL锁的基本介绍
3、2 常见的3种锁
4、mvcc
4、1概念
4、2基于锁的并发控制流程
4、3接下来以innoDB为前提,举例说明mvcc
5、存储引擎
5、1概述
5、2InnoDB存储引擎
5、3MyISAM存储引擎
5、4MEMORY存储引擎
5、5MERGE存储引擎
6、分库分表
6、1分库分表出现的背景
6、2垂直切分
6、3水平切分(重点)
6、4数据切分导致的一些问题
6、5分库分表之后,数据源的管理是系统实现的关键。
7、主从复制,读写分离
7、1概述
7、2主从复制的结果?
7、3复制的几种方式(策略)?
7、4主从复制的好处?
7、5如何实现主从复制的?
8、索引
8、1 索引是什么?
8、2 索引有哪几种类型?
8、3 索引在哪里用?
8、4 索引的数据结构的演变:
9、MySQL性能优化
9、1 SQL的性能优化之索引优化

https://blog.csdn.net/Song_JiangTao/article/details/104812475

在这里插入图片描述

2.2 Redis

面经:

Redis为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路 I/O 复用模型,非阻塞 IO;

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

Redis有哪些数据类型

Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求

在这里插入图片描述

redis的分布式锁实现

在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。

当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

返回值:设置成功,返回 1 。设置失败,返回 0 。

使用SETNX完成同步锁的流程及事项如下:

使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功

为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间

释放锁,使用DEL命令将锁数据删除

2.2.1 Redis简介

Redis 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。

Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。

与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

2.2.2 Redis的优缺点

优点

  • 读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
  • 支持数据持久化,支持AOF和RDB两种持久化方式。
  • 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
  • 数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
  • 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。

缺点

  • 数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
  • Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
  • 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
  • Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。

2.2.3 使用场景

计数器

可以对 String 进行自增自减运算,从而实现计数器功能。

Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。

缓存

将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。

会话缓存

可以使用 Redis 来统一存储多台应用服务器的会话信息。

当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。

全页缓存(FPC)

除基本的会话token之外,Redis还提供很简便的FPC平台。

以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

查找表

例如 DNS 记录就很适合使用 Redis 进行存储。

查找表和缓存类似,也是利用了 Redis 快速的查找特性。但是查找表的内容不能失效,而缓存的内容可以失效,因为缓存不作为可靠的数据来源。

消息队列(发布/订阅功能)

List 是一个双向链表,可以通过 lpush 和 rpop 写入和读取消息

不过最好使用 Kafka、RabbitMQ 等消息中间件。

分布式锁实现

在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。

可以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

其它

Set 可以实现交集、并集等操作,从而实现共同好友等功能。

ZSet 可以实现有序性操作,从而实现排行榜等功能。

2.2.4 持久化

Redis 是内存型数据库,为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置,需要将内存中的数据持久化到硬盘上。

Redis 提供了RDB和AOF两种持久化方式。默认是只开启RDB,当Redis重启时,它会优先使用AOF文件来还原数据集。

RDB 持久化(快照持久化):将某个时间点的所有数据都存放到硬盘上。

缺点:如果系统发生故障,将会丢失最后一次创建快照之后的数据。如果数据量很大,保存快照的时间会很长。

AOF 持久化:将写命令添加到 AOF 文件(Append Only File)的末尾。

默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:

appendonly yes

在Redis的配置文件中存在三种同步方式
在这里插入图片描述
appendfsync always 可以实现将数据丢失减到最少,不过这种方式需要对硬盘进行大量的写入而且每次只写入一个命令,十分影响Redis的速度。另外使用固态硬盘的用户谨慎使用appendfsync always选项,因为这会明显降低固态硬盘的使用寿命。

appendfsync everysec 为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

appendfsync no 选项一般不推荐,这种方案会使Redis丢失不定量的数据而且如果用户的硬盘处理写入操作的速度不够的话,那么当缓冲区被等待写入的数据填满时,Redis的写入操作将被阻塞,这会导致Redis的请求速度变慢。

随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。

虽然AOF持久化非常灵活地提供了多种不同的选项来满足不同应用程序对数据安全的不同要求,但AOF持久化也有缺陷——AOF文件的体积太大。

这样就引出了

重写/压缩AOF

用户可以向Redis发送 BGREWRITEAOF命令 ,这个命令会通过移除AOF文件中的冗余命令来重写(rewrite)AOF文件来减小AOF文件的体积。

Redis 4.0 对持久化机制的优化

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差。

如何选择合适的持久化方式

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

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

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

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

2.2.5 过期删除策略

我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。

注:对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。

如果一个键是过期的,那它到了过期时间之后是不是马上就从内存中被被删除呢?如果不是,那过期后到底什么时候被删除呢?

其实有三种不同的删除策略:

(1)立即删除。在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。【数据“新鲜”,删除操作对cpu要求高,压力大】

(2)惰性删除。键过期了就过期了,不管。每次从dict字典中按key取值时,先检查此key是否已经过期,如果过期了就删除它,并返回nil,如果没过期,就返回键值。【浪费内存 没用的数据得不到立即删除,但是cpu压力小】

(3)定时删除。每隔一段时间,对expires字典进行检查,删除里面的过期键。【这是一种对上面两种优缺点这种的处理方法】

可以看到,第二种为被动删除,第一种和第三种为主动删除,且第一种实时性更高。

redis使用的过期键值删除策略是:惰性删除加上定期删除,两者配合使用。

2.2.6 Redis数据淘汰策略

在这里插入图片描述
redis中如何设置数据淘汰策略:

    127.0.0.1:6379> CONFIG GET maxmemory-policy 
    1) "maxmemory" 
    2) "0"
    127.0.0.1:6379> CONFIG SET maxmemory-policy allkeys-lru
    OK 
    127.0.0.1:6379> CONFIG GET maxmemory-policy
    1) "maxmemory-policy"
    2) "allkeys-lru" 

2.2.7 Redis和Memcached的区别

在这里插入图片描述
2.2.8 事务

Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能

事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,可以减少客户端与服务器之间的网络通信次数从而提升性能。

在传统的关系式数据库中,常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。

2.2.9 事件

Redis 服务器是一个事件驱动程序。

文件事件

服务器通过套接字与客户端或者其它服务器进行通信,文件事件就是对套接字操作的抽象。

Redis 基于 Reactor 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器。

在这里插入图片描述
时间事件

服务器有一些操作需要在给定的时间点执行,时间事件是对这类定时操作的抽象。

时间事件又分为:

定时事件:是让一段程序在指定的时间之内执行一次

周期性事件:是让一段程序每隔指定时间就执行一次

目前Redis只使用周期性事件,而没有使用定时事件。 一个事件时间主要由三个属性组成:

id:服务器为时间事件创建的全局唯一ID

when:毫秒精度的UNIX时间戳,记录了时间事件的到达时间

timeProc:时间事件处理器,一个函数

实现服务器将所有时间事件都放在一个无序链表中,每当时间事件执行器运行时,遍历整个链表,查找所有已到达的时间事件,并调用相应的事件处理器。(该链表为无序链表,不按when属性的大小排序)

事件的调度与执行

服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能一直监听,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。

在这里插入图片描述
2.2.10 Sentinel

Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。

2.2.11 分片

分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。

假设有 4 个 Redis 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,… ,有不同的方式来选择一个指定的键存储在哪个实例中。

最简单的方式是范围分片,例如用户 id 从 0~1000 的存储到实例 R0 中,用户 id 从 1001~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。

还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。

根据执行分片的位置,可以分为三种分片方式:

  • 客户端分片:客户端使用一致性哈希等算法决定键应当分布到哪个节点。
  • 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。
  • 服务器分片:Redis Cluster。

2.2.12 复制

通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。

一个从服务器只能有一个主服务器,并且不支持主主复制。

连接过程

主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令

从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令

主服务器每执行一次写命令,就向从服务器发送相同的写命令

主从链

随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。

在这里插入图片描述

2.2.13 Redis中缓存雪崩、缓存穿透

2.2.13.1 缓存雪崩

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

解决方案

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

一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。

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

2.2.13.2 缓存穿透

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

解决方案

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击

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

附加

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。

Bitmap: 典型的就是哈希表

缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器(推荐)

就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过程。

它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。

Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突,我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。

Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

2.2.13.3 缓存击穿

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

解决方案

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

加互斥锁,互斥锁

2.2.13.4 缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决方案

直接写个缓存刷新页面,上线时手工操作一下;

数据量不大,可以在项目启动的时候自动进行加载;

定时刷新缓存;

2.2.13.5 缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

缓存降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

2.2.13.6 热点数据和冷数据

热点数据,缓存才有价值

对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不大。频繁修改的数据,看情况考虑使用缓存

对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。

数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那就没有太大价值了。

那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同步保存到Redis缓存,减少数据库压力。

2.2.13.7 缓存热点key

缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

2.3 Oracle

Oracle和MySQL的区别

一、宏观上:

1、Oracle是大型的数据库而Mysql是中小型数据库;Mysql是开源的,Oracle是收费的,且价格昂贵。

2、Oracle支持大并发,大访问量,是OLTP的最好的工具。

3、安装占用的内存也是有差别,Mysql安装完成之后占用的内存远远小于Oracle所占用的内存,并且Oracle越用所占内存也会变多。

二、微观上:

1、对于事务的支持

Mysql对于事务默认是不支持的,只是有某些存储引擎中如:innodb可以支持;而Oracle对于事物是完全支持的。

2、并发性

什么是并发性?并发性是OLTP(On-Line Transaction Processing联机事务处理过程)数据库最重要的特性,并发性涉及到资源的获取、共享与锁定。

Mysql,既支持表锁,也支持行级锁。表锁,对资源锁定的力度很大,如果一个session对一个表加锁时间过长,会让其他session无法更新此表的数据。

Oracle使用行级锁,对资源锁定的力度要小很多,只是锁定sql需要的资源,并且加锁是在数据库中的数据行上,不依赖于索引。所以oracle对并发性的支持要好很多。

3、数据的持久性

Oracle保证提交的事务均可以恢复,因为Oracle把提交的sql操作线写入了在线联机日志文件中,保存到磁盘上,如果出现数据库或者主机异常重启,重启Oracle可以靠联机在线日志恢复客户提交的数据。

Mysql默认提交sql语句,但是如果更新过程中出现db或者主机重启的问题,也可能会丢失数据。

4、事务隔离级别

MySQL是repeatable read的隔离级别,而Oracle是read commited的隔离级别,同时二者都支持serializable串行化事务隔离级别,可以实现最高级别的。

读一致性。每个session提交后其他session才能看到提交的更改。Oracle通过在undo表空间中构造多版本数据块来实现读一致性,每个session 查询时,如果对应的数据块发生变化,Oracle会在undo表空间中为这个session构造它查询时的旧的数据块。

MySQL没有类似Oracle的构造多版本数据块的机制,只支持read commited的隔离级别。一个session读取数据时,其他session不能更改数据,但可以在表最后插入数据。session更新数据时,要加上排它锁,其他session无法访问数据

5、提交方式

Oracle默认不自动提交,需要手动提交。Mysql默认自动提交。

6、逻辑备份

Mysql逻辑备份是要锁定数据,才能保证备份的数据是一致的,影响业务正常的DML(数据操纵语言Data Manipulation Language)使用;Oracle逻辑备份时不锁定数据,且备份的数据是一致的。

7、sql语句的灵活性

mysql对sql语句有很多非常实用而方便的扩展,比如limit功能(分页),insert可以一次插入多行数据;Oracle在这方面感觉更加稳重传统一些,Oracle的分页是通过伪列和子查询完成的,插入数据只能一行行的插入数据。

8、数据复制

MySQL:复制服务器配置简单,但主库出问题时,丛库有可能丢失一定的数据。且需要手工切换丛库到主库。

Oracle:既有推或拉式的传统数据复制,也有dataguard的双机或多机容灾机制,主库出现问题是,可以自动切换备库到主库,但配置管理较复杂。

9、分区表和分区索引

MySQL的分区表还不太成熟稳定;Oracle的分区表和分区索引功能很成熟,可以提高用户访问db的体验。

10、售后与费用

Oracle是收费的,出问题找客服;Mysql是免费的的,开源的,出问题自己解决。

11、权限与安全

Oracle的权限与安全概念比较传统,中规中矩;MySQL的用户与主机有关,感觉没有什么意义,另外更容易被仿冒主机及ip有可乘之机。

12、性能诊断方面

Oracle有各种成熟的性能诊断调优工具,能实现很多自动分析、诊断功能。比如awr、addm、sqltrace、tkproof等 ;MySQL的诊断调优方法较少,主要有慢查询日志。

3. Jvm 虚拟机

面经:

1、jvm 新生代和老年代的默认配比是多少?

新生代:老年代 = 1:2

新生代中:eden:form:to=8:1:1

2、jvm垃圾收集器使用的算法?

3、常用的设置

堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

4、java一个对象创建到死亡的过程

1、检查该类是否已经加载,解析和初始化过,如果没有先进行类加载
2、为新生对象分配内存
3、构造函数的执行,javap反汇编指令编译的一系列指令的执行。
4、之后就是对象的使用
5、对象是否已死的判定?(是否实现finalize()方法,在方法中是否实现自我救赎?)
6、对象的两次标记到回收
如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记

筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。如果这个对象被判定为确有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法。这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束。这样做的原因是,如果某个对象的finalize()方法执行缓慢,或者更极端地发生了死循环,将很可能导致F-Queue队列中的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后收集器将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了。从代码清单3-2中我们可以看到一个对象的finalize()被执行,但是它仍然可以存活
https://blog.csdn.net/qq_40704861/article/details/97502937

3.1 Jvm虚拟机家族

在这里插入图片描述

3.2 HotSpot VM

3.2.1 概述

HotSpot VM 包括一个解释器和两个编译器(Client 和 Server,二选一的),解释与编译混合执行模式,默认启动解释执行。

  • 编译器:Java源代码被编译器编译成Class文件(字节码),Java字节码在运行时可以被动态编译(JIT)成本地代码(前提是解释与编译混合执行模式且虚拟机不是刚启动时)。
    • Server启动慢,占用内存多,执行效率高,适用于服务器端应用;
    • Client启动快,占用内存小,执行效率没有server快,默认情况下不进行动态编译,适用于桌面应用程序。
  • 解释器:解释器用来解释Class文件(字节码),Java是解释语言。监视器:决定哪些程序不编译,哪些编译,哪些优化。
    java是解释性语言,指令性质:你需要告诉机器一步步怎么做,描述性语言则不同,例如sql语言,他只关心他要什么,把自己想要的东西告诉机器,让机器自己去拿,具体怎么拿?他不管。在jdk1.8的 stream集合操作就是这种思想的体现

3.2.2 热点代码探测

HotSpot之所以被称为hot spot是因为它有一个技术点:热点代码探测

  • 做什么:见名知意
  • 热点探测的对象:
    • 被多次调用的方法
    • 被多次调用的循环体。
  • 处理原则:为每个方法(甚至是代码块)建立计数器,执行次数超过阈值就认为是“热点方法”。
    • 计数器(相对频率,在一定的时间限度内,该方法被调用的次数,超出该时间段未达到阈值,计数器的值减半-----热度衰减机制,热度衰减的周期称为半衰期,该过程在垃圾收集时顺便执行)
    • 阈值(默认 : 1500 on client, 10000 on server )
      • -XX:CounterHalfLifeTime 设定半衰期周期时间
      • -XX:CompileThreadhold 设定阈值
        在这里插入图片描述

3.2.3 运行时数据区域

3.2.3.1 堆
  • 线程共享
  • 堆(heap)它是Java虚拟机用来存储对象实例的,比我们在开发过程使用的new对象,只要通过new创建的对象(即所有的对象实例)的内存的对象都在堆分配,(注意,随着逃逸分析技术日渐强大,栈上分配,标量替换等优化手段,已经导致这个特性发生了些变化,所以说 java对象都是分配在堆上也渐渐变得不是那么绝对了)注意一点的是堆中的对象内存需要等待垃圾器(GC)进行回收,也是Java虚拟机共享区。 随着时间的发展,堆里面的经典分区(一个新生代,两个survivor区,一个老年区)也发生了变化,在虚拟机HotSpot里面也出现了不采用分代设计的新垃圾收集器。
  • 堆内存中的成员变量是随着对象的产生而产生。随着对象的消失而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。
3.2.3.2 程序计数器
  • 线程私有
  • 程序计数器是一块较小的内存空间,它的作用可以看作是 当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。 此内存区域是唯一一个在《java虚拟机规范》中没有规定任何oom错误的情况的区域
3.2.3.3 本地方法栈
  • 线程私有
  • 本地方法栈为虚拟机执行native方法服务, 这是本地方法栈和虚拟机栈的区别。 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出Stack OverflowError异常和OutOfMemoryError异常
3.2.3.4 虚拟机栈(java方法栈)
  • 线程私有
  • 之所以被称为“java方法栈”是因为,虚拟机栈是为java方法(也就是字节码)服务的,每个方法被执行的时候,都会同步创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法被调用直到执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 这个内存区域规定了两类异常情况,1、如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;2、如果java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存时,会抛出OutOfMemoryError异常。
3.2.3.5 方法区(元空间)
  • 线程共享
  • 类中存储的信息
    • 1、类的信息(名称、修饰符等)
    • 2、类中的静态变量
    • 3、类中定义为final类型的常量
    • 4、类中的Field信息
    • 5、类中的方法信息
  • JDK 6 时,String等字符串常量的信息是置于方法区中的,但是到了JDK 7 时,已经移动到了Java堆。 到了JDK1.8提出废弃永久代(即方法区),而提出了元空间(Metaspace的概念),不再与堆连续,而是直接存在本地内存中,也就是机器的内存。理论上机器的内存有多大,元空间就可以有多大。但可以通过参数来设置元空间的大小

类加载过程:
加载

1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入
口。

验证

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚
拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。

准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初
始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区
本身是一个逻辑上的区域,在JDK 7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这
种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中

解析

解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,符号引用在第6章讲解Class
文件格式的时候已经出现过多次,在Class文件中它以CONSTANT_Class_info、
CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现

初始化

类的初始化阶段是类加载过程的最后一个步骤,之前介绍的几个类加载的动作里,除了在加载阶
段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控
制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程
序。

在这里插入图片描述

3.2.3 垃圾回收

3.2.3.1 概述

GC:Garbage Collection 垃圾回收

垃圾收集的历史远远比Java(1995)久远,1960年诞生的Lisp是第一门开始使用内存动态分配和垃圾收集技术的语言。

3.2.3.2 回收策略
  • 引用计数算法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

在这里插入图片描述在这里插入图片描述

  • 可达性分析算法
    • 算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

在这里插入图片描述
在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:

  • 1、栈帧中的本地变量表中的引用对象。
  • 2、在元空间中 类 静态属性引用的对象。
  • 3、在元空间中常量引用的对象。
  • 4、在本地方法栈中引用的对象。
  • 5、Java虚拟机内部的引用。
  • 6、所有被同步锁(synchronized关键字)持有的对象。
  • 7、反映Java虚拟机内部情况的JMXBean、本地代码缓存等
3.2.3.3 垃圾回收算法

一些经典的算法设计的过程总是曲折的。Hotspot中的垃圾回收算法,就是一直在迭代,演化

刚开始的假说:

1)弱分代假说:绝大多数对象都是朝生夕灭的。

2)强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。

3)跨代引用假说:跨代引用相对于同代引用来说仅占极少数。

抽象出的 分代收集理论

名为理论:实际上就是一套符合大多数程序运行实际情况的经验法则。

在这里插入图片描述
整个年轻代和老年代的默认比例是:1:2

一个程序在实际运行中的内存变化

在这里插入图片描述

2.2.3.3.1 标记-清除算法

在这里插入图片描述

  • 最基础的收集算法“标记-复制”
  • “标记-整理”算法均由此算法改造
  • 缺点:
    • 1、执行效率不稳定:Java堆中有大量的对象,如果这些对象都需要回收的话,就会由于多次的标记和回收导致效率低下!
    • 2、内存空间的碎片化问题:标记、清除之后会产生大量不连续的内存碎片,这样就会导致,在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作
2.2.3.3.2 标记-复制算法

在这里插入图片描述

针对标记清除算法的缺陷,我们也看到了复制算法的优势:

1、空间连续,不在碎片化

缺点也显而易见:

1、空间浪费

2、内存中存活对象多,将会产生大量的内存间复制的开销

2.2.3.3.1 标记-整理算法

在这里插入图片描述
标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动式的。

是否移动回收后的存活对象是一项优缺点并存的风险决策,如果移动:如果存在大量的存活对象,那么,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作“必须”全程暂停用户应用程序才能进行

3.2.3.4 垃圾收集器
3.2.3.4.1 经典垃圾收集器

Serial收集器、

  • 新生代GC
  • 最基础的收集器
  • “单线程”的收集器
    “单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
  • Client 模式下 JVM 的默认选项

请添加图片描述
Serial Old收集器、

  • 和Serial对应,Serial old是老年代的收集器

ParNew收集器、

  • 新生代的GC
  • 基于标记-复制算法
  • 多线程并行版本的GC
    除此之外,其他与Serial收集器相比并没有太多创新之处,但它却是不少运行在server模式下的HotSpot虚拟机,和它经常合作的GC是:CMS收集器

请添加图片描述
Parallel Old收集器、

Parallel Scavenge收集器、

  • 新生代的GC
  • 基于标记-复制算法
  • 多线程并行版本的GC
  • 专注于“吞吐量”

CMS收集器、

新生代和老年代混合的GCCMS收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网网站或者基于浏览器的B/S系统的服务端上,这类应用通常都会较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验。CMS收集器就非常符合这类应用的需求

  • 新生代和老年代混合的GC
  • 最短回收停顿时间是它的目标。
  • 基于标记-清除算法
  • 浮动垃圾的产生
    请添加图片描述
    Garbage First收集器(G1)

新生代和部分老年代的GC服务端垃圾收集器。HotSpot开发团队最初赋予它的期望是(在比较长期的)替换掉JDK 5中发布的CMS收集器,JDK 9发布时,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,G1它可以面向堆内存任何部分来组成回收集

  • 新生代和部分老年代的GC
  • 服务端垃圾收集器。
  • G1可以面向堆内存任何部分来组成回收集

请添加图片描述
3.2.3.4.2 低延时垃圾收集器

Shenandoah收集器、

ZGC收集器

请添加图片描述
GC(垃圾收集器)参数总结

https://blog.csdn.net/para_Arya/article/details/90483688

Major GC和Full GC的区别是什么?触发条件呢?R大的回答

https://www.zhihu.com/question/41922036

4. HashMap

4.1 hashcode和equals的关系

1、如果两个对象equals相等,那么这两个对象的HashCode一定也相同

2、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

4.2 哈希冲突

  • 也叫哈希碰撞,两个不同的元素,通过哈希函数得出的实际存储地址相同

4.3 jdk 1.7中的HashMap

4.3.1 HashMap的实现原理

HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。(其实所谓Map其实就是保存了两个对象之间的映射关系的一种集合)

    //HashMap的主干数组,可以看到就是一个Entry数组,初始值为空数组{},主干数组的长度一定是2的次幂。
    //至于为什么这么做,后面会有详细分析。
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

Entry是HashMap中的一个静态内部类。代码如下

  static class Entry<K,V> implements Map.Entry<K,V> {
      final K key;
      V value;
      Entry<K,V> next;//存储指向下一个Entry的引用,单链表结构
      int hash;//对key的hashcode值进行hash运算后得到的值,存储在Entry,避免重复计算

      /**
       * Creates new entry.
       */
      Entry(int h, K k, V v, Entry<K,V> n) {
          value = v;
          next = n;
          key = k;
          hash = h;
      } 

简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

其他几个重要字段

    /**实际存储的key-value键值对的个数*/
    transient int size;
    
    /**阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,
    threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到*/
    int threshold;
    
    /**负载因子,代表了table的填充度有多少,默认是0.75
    加载因子存在的原因,还是因为减缓哈希冲突,如果初始桶为16,等到满16个元素才扩容,某些桶里可能就有不止一个元素了。
    所以加载因子默认为0.75,也就是说大小为16的HashMap,到了第13个元素,就会扩容成32。
    */
    final float loadFactor;
    
    /**HashMap被改变的次数,由于HashMap非线程安全,在对HashMap进行迭代时,
    如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),
    需要抛出异常ConcurrentModificationException*/
    transient int modCount;

接下来我们来看看put操作的实现

    public V put(K key, V value) {
            //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,
            //此时threshold为initialCapacity 默认是1<<4(24=16)
            if (table == EMPTY_TABLE) {
                inflateTable(threshold);
            }
           //如果key为null,存储位置为table[0]或table[0]的冲突链上
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key);//对key的hashcode进一步计算,确保散列均匀
            int i = indexFor(hash, table.length);//获取在table中的实际位置
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value
                Object k;
                if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
            modCount++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败
            addEntry(hash, key, value, i);//新增一个entry
            return null;
        }

4.3.2 为什么HashMap的数组长度一定保持2的次幂

HashMap的数组长度一定保持2的次幂,比如16的二进制表示为 10000,那么length-1就是15,二进制为01111,同理扩容后的数组长度为32,二进制表示为100000,length-1为31,二进制表示为011111。从下图可以我们也能看到这样会保证低位全为1,而扩容后只有一位差异,也就是多出了最左位的1,这样在通过 h&(length-1)的时候,只要h对应的最左边的那一个差异位为0,就能保证得到的新的数组索引和老数组索引一致(大大减少了之前已经散列良好的老数组的数据位置重新调换),个人理解。

hash函数

/**这是一个神奇的函数,用了很多的异或,移位等运算
 对key的hashcode进一步进行计算以及二进制位的调整等来保证最终获取的存储位置尽量分布均匀*/
 final int hash(Object k) {
         int h = hashSeed;
         if (0 != h && k instanceof String) {
             return sun.misc.Hashing.stringHash32((String) k);
         }
 
         h ^= k.hashCode();
 
         h ^= (h >>> 20) ^ (h >>> 12);
         return h ^ (h >>> 7) ^ (h >>> 4);
     }
 12345678910111213

以上hash函数计算出的值,通过indexFor进一步处理来获取实际的存储位置

/**
* 返回数组下标
*/
static int indexFor(int h, int length) {
   return h & (length-1);
}

以上内容摘自:https://blog.csdn.net/woshimaxiao1/article/details/83661464

4.4 jdk1.8中的HashMap

在这里插入图片描述

在这里插入图片描述

4.4.1 putVal方法的主要逻辑

1、如果数组还没有初始化(数组长度是0),则先初始化

2、通过hash方法计算key的hash值,进而计算得到应该放置到数组的位置

3、如果该位置为空,则直接放置此处

4、如果该位置不为空,而且元素是红黑树,则插入到其中

5、如果是链表,则遍历链表,如果找到相等的元素则替换,否则插入到链表尾部

6、如果链表的长度大于或等于8,则将链表转成红黑树

4.4.2 总结

1、HashMap底层是用数组+双向链表+红黑树实现的

2、插入元素的时候,首先通过一个hash方法计算得到key的哈希值,进而计算出待插入的位置

3、如果该位置为空,则直接插入(包装成Node)

4、如果该位置有值,则依次遍历。比较的规则是,hash值相同,key值相等的元素视为相同,则用新值替换旧值并返回旧值。

5、如果该位置的元素是红黑树结构,则同理,查找,找到则替换,没找到则插入。

4.4.3划重点

JDK1.8中HashMap与JDK1.7中有很多地方不一样

1、1.8中引入了红黑树,而1.7中没有

2、1.8中元素是插在链表的尾部,而1.7中新元素是插在链表的头部

3、扩容的时候,1.8中不会出现死循环,而1.7中容易出现死循环,而且链表不会倒置

以上内容摘自:https://www.cnblogs.com/cjsblog/p/8207211.html

5. TCP / IP / HTTP / HTTPS

5.1 概述

从字面意义上讲,有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称。

具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。他们与 TCP 或 IP 的关系紧密,是互联网必不可少的组成部分。TCP/IP 一词泛指这些协议,因此,有时也称 TCP/IP 为网际协议群。

互联网进行通信时,需要相应的网络协议,TCP/IP 原本就是为使用互联网而开发制定的协议族。因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议。

在这里插入图片描述
5.2 数据包

包、帧、数据包、段、消息

以上五个术语都用来表述数据的单位,大致区分如下:

  • 包可以说是全能性术语;
  • 帧用于表示数据链路层中包的单位;
  • 数据包是 IP 和 UDP 等网络层以上的分层中包的单位;
  • 段则表示 TCP 数据流中的信息;
  • 消息是指应用协议中数据的单位。

每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层必要的信息,如发送的目标地址以及协议相关信息。通常,为协议提供的信息为包首部,所要发送的内容为数据。在下一层的角度看,从上一层收到的包全部都被认为是本层的数据。
在这里插入图片描述
网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据。首部的结构由协议的具体规范详细定义。在数据包的首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解该协议必要的信息以及所要处理的数据。包首部就像协议的脸。

5.3 数据处理流程

下图以用户 a 向用户 b 发送邮件为例子:
在这里插入图片描述

① 应用程序处理

首先应用程序会进行编码处理,这些编码相当于 OSI 的表示层功能;

编码转化后,邮件不一定马上被发送出去,这种何时建立通信连接何时发送数据的管理功能,相当于 OSI 的会话层功能。

② TCP 模块的处理

TCP 根据应用的指示,负责建立连接、发送数据以及断开连接。TCP 提供将应用层发来的数据顺利发送至对端的可靠传输。为了实现这一功能,需要在应用层数据的前端附加一个 TCP 首部。

③ IP 模块的处理

IP 将 TCP 传过来的 TCP 首部和 TCP 数据合起来当做自己的数据,并在 TCP 首部的前端加上自己的 IP 首部。IP 包生成后,参考路由控制表决定接受此 IP 包的路由或主机。

④ 网络接口(以太网驱动)的处理

从 IP 传过来的 IP 包对于以太网来说就是数据。给这些数据附加上以太网首部并进行发送处理,生成的以太网数据包将通过物理层传输给接收端。

⑤ 网络接口(以太网驱动)的处理

主机收到以太网包后,首先从以太网包首部找到 MAC 地址判断是否为发送给自己的包,若不是则丢弃数据。

如果是发送给自己的包,则从以太网包首部中的类型确定数据类型,再传给相应的模块,如 IP、ARP 等。这里的例子则是 IP 。

⑥ IP 模块的处理

IP 模块接收到 数据后也做类似的处理。从包首部中判断此 IP 地址是否与自己的 IP 地址匹配,如果匹配则根据首部的协议类型将数据发送给对应的模块,如 TCP、UDP。这里的例子则是 TCP。

另外吗,对于有路由器的情况,接收端地址往往不是自己的地址,此时,需要借助路由控制表,在调查应该送往的主机或路由器之后再进行转发数据。

⑦ TCP 模块的处理

在 TCP 模块中,首先会计算一下校验和,判断数据是否被破坏。然后检查是否在按照序号接收数据。***检查端口号,确定具体的应用程序。数据被完整地接收以后,会传给由端口号识别的应用程序。

⑧ 应用程序的处理

接收端应用程序会直接接收发送端发送的数据。通过解析数据,展示相应的内容。

5.4 传输层中的 TCP 和 UDP

TCP/IP 中有两个具有代表性的传输层协议,分别是 TCP 和 UDP。

  • TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,当应用程序采用 TCP 发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。TCP 为提供可靠性传输,实行“顺序控制”或“重发控制”机制。此外还具备“流控制(流量控制)”、“拥塞控制”、提高网络利用率等众多功能。
  • UDP 是不具有可靠性的数据报协议。细微的处理它会交给上层的应用去完成。在 UDP 的情况下,虽然可以确保发送消息的大小,却不能保证消息一定会到达。因此,应用有时会根据自己的需要进行重发处理。
  • TCP 和 UDP 的优缺点无法简单地、绝对地去做比较:TCP 用于在传输层有必要实现可靠传输的情况;而在一方面,UDP 主要用于那些对高速传输和实时性有较高要求的通信或广播通信。TCP 和 UDP 应该根据应用的目的按需使用。

5.4.1 端口号

数据链路和 IP 中的地址,分别指的是 MAC 地址和 IP 地址。前者用来识别同一链路中不同的计算机,后者用来识别 TCP/IP 网络中互连的主机和路由器。在传输层也有这种类似于地址的概念,那就是端口号。端口号用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。

1.1 根据端口号识别应用

一台计算机上同时可以运行多个程序。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确地将数据传输。

在这里插入图片描述

1.2 通过 IP 地址、端口号、协议号进行通信识别

  • 仅凭目标端口号识别某一个通信是远远不够的

在这里插入图片描述
在这里插入图片描述

  • ① 和② 的通信是在两台计算机上进行的。它们的目标端口号相同,都是80。这里可以根据源端口号加以区分。
    ③ 和 ① 的目标端口号和源端口号完全相同,但它们各自的源 IP 地址不同。
    此外,当 IP 地址和端口号全都一样时,我们还可以通过协议号来区分(TCP 和 UDP)。

1.3 端口号的确定

  • 标准既定的端口号:这种方法也叫静态方法。它是指每个应用程序都有其指定的端口号。但并不是说可以随意使用任何一个端口号。例如 HTTP、FTP、TELNET 等广为使用的应用协议中所使用的端口号就是固定的。这些端口号被称为知名端口号,分布在 0~1023 之间;除知名端口号之外,还有一些端口号被正式注册,它们分布在 1024~49151 之间,不过这些端口号可用于任何通信用途。
  • 时序分配法:服务器有必要确定监听端口号,但是接受服务的客户端没必要确定端口号。在这种方法下,客户端应用程序完全可以不用自己设置端口号,而全权交给操作系统进行分配。动态分配的端口号范围在 49152~65535 之间。

1.4 端口号与协议

  • 端口号由其使用的传输层协议决定。因此,不同的传输层协议可以使用相同的端口号。
  • 此外,那些知名端口号与传输层协议并无关系。只要端口一致都将分配同一种应用程序进行处理。

5.4.2 UDP

  • UDP 不提供复杂的控制机制,利用 IP 提供面向无连接的通信服务。
  • 并且它是将应用程序发来的数据在收到的那一刻,立即按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况,UDP 也无法进行流量控制等避免网络拥塞行为。
  • 此外,传输途中出现丢包,UDP 也不负责重发。
  • 甚至当包的到达顺序出现乱序时也没有纠正的功能。
  • 如果需要以上的细节控制,不得不交由采用 UDP 的应用程序去处理。
  • UDP 常用于一下几个方面:1.包总量较少的通信(DNS、SNMP等);2.视频、音频等多媒体通信(即时通信);3.限定于 LAN 等特定网络中的应用通信;4.广播通信(广播、多播)。

5.4.3 TCP

  • TCP 与 UDP 的区别相当大。它充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在 UDP 中都没有。
  • 此外,TCP 作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。
  • 根据 TCP 的这些机制,在 IP 这种无连接的网络上也能够实现高可靠性的通信( 主要通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现)。
5.4.3.1 三次握手(重点)
  • TCP 提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。
  • 所谓三次握手是指建立一个 TCP 连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发。
    下面来看看三次握手的流程图:

在这里插入图片描述

  • 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
  • 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
  • 第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
    SYN:同步序列编号(Synchronize Sequence Numbers)。是TCP/IP建立连接时使用的握手信号。
    ACK:(Acknowledge character)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符
    seq:占 4 字节,序号范围[0,2^32-1],序号增加到 2^32-1 后,下个序号又回到 0。TCP 是面向字节流的,通过 TCP 传送的字节流中的每个字节都按顺序编号,而报头中的序号字段值则指的是本报文段数据的第一个字节的序号。
5.4.3.2 四次挥手(重点)
  • 四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。
  • 由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

下面来看看四次挥手的流程图:

在这里插入图片描述

  • 中断连接端可以是客户端,也可以是服务器端。
  • 第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
  • 第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
  • 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
  • 第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次握手。

上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况。

具体流程如下图:

在这里插入图片描述

5.4.3.3 通过序列号与确认应答提高可靠性
  • 在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个已收到消息的通知。这个消息叫做确认应答(ACK)。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。
  • 在一定时间内没有等待到确认应答,发送端就可以认为数据已经丢失,并进行重发。由此,即使产生了丢包,仍然能够保证数据能够到达对端,实现可靠传输。
  • 未收到确认应答并不意味着数据一定丢失。也有可能是数据对方已经收到,只是返回的确认应答在途中丢失。这种情况也会导致发送端误以为数据没有到达目的地而重发数据。
  • 此外,也有可能因为一些其他原因导致确认应答延迟到达,在源主机重发数据以后才到达的情况也屡见不鲜。此时,源主机只要按照机制重发数据即可。
  • 对于目标主机来说,反复收到相同的数据是不可取的。为了对上层应用提供可靠的传输,目标主机必须放弃重复的数据包。为此我们引入了序列号。
  • 序列号是按照顺序给发送数据的每一个字节(8位字节)都标上号码的编号。接收端查询接收数据 TCP 首部中的序列号和数据的长度,将自己下一步应该接收的序列号作为确认应答返送回去。通过序列号和确认应答号,TCP 能够识别是否已经接收数据,又能够判断是否需要接收,从而实现可靠传输。
    在这里插入图片描述
5.4.3.4 重发超时的确定
  • 重发超时是指在重发数据之前,等待确认应答到来的那个特定时间间隔。如果超过这个时间仍未收到确认应答,发送端将进行数据重发。最理想的是,找到一个最小时间,它能保证“确认应答一定能在这个时间内返回”。
  • TCP 要求不论处在何种网络环境下都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。为此,它在每次发包时都会计算往返时间及其偏差。将这个往返时间和偏差时间相加,重发超时的时间就是比这个总和要稍大一点的值。
  • 在 BSD 的 Unix 以及 Windows 系统中,超时都以0.5秒为单位进行控制,因此重发超时都是0.5秒的整数倍。不过,最初其重发超时的默认值一般设置为6秒左右。
  • 数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
  • 此外,数据也不会被、反复地重发。达到一定重发次数之后,如果仍没有任何确认应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接。并且通知应用通信异常强行终止。
5.4.3.5 以段为单位发送数据
  • 在建立 TCP 连接的同时,也可以确定发送数据包的单位,我们也可以称其为“消息长度”(MSS)。最理想的情况是,消息长度正好是 IP 中不会被分片处理的***数据长度。
  • TCP 在传送大量数据时,是以 MSS 的大小将数据进行分割发送。进行重发时也是以 MSS 为单位。
  • MSS 在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在 TCP 首部中写入 MSS 选项,告诉对方自己的接口能够适应的 MSS 的大小。然后会在两者之间选择一个较小的值投入使用。
5.4.3.6 利用窗口控制提高速度
  • TCP 以1个段为单位,每发送一个段进行一次确认应答的处理。这样的传输方式有一个缺点,就是包的往返时间越长通信性能就越低。
  • 为解决这个问题,TCP 引入了窗口这个概念。确认应答不再是以每个分段,而是以更大的单位进行确认,转发时间将会被大幅地缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。如下图所示:
    在这里插入图片描述
  • 窗口控制
  • 窗口大小就是指无需等待确认应答而可以继续发送数据的值。上图中窗口大小为4个段。这个机制实现了使用大量的缓冲区,通过对多个段同时进行确认应答的功能。
5.4.3.7 滑动窗口控制

在这里插入图片描述

  • 上图中的窗口内的数据即便没有收到确认应答也可以被发送出去。不过,在整个窗口的确认应答没有到达之前,如果其中部分数据出现丢包,那么发送端仍然要负责重传。为此,发送端主机需要设置缓存保留这些待被重传的数据,直到收到他们的确认应答。
  • 在滑动窗口以外的部分包括未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再进行重发,此时数据就可以从缓存区清除。
  • 收到确认应答的情况下,将窗口滑动到确认应答中的序列号的位置。这样可以顺序地将多个段同时发送提高通信性能。这种机制也别称为滑动窗口控制。

5.4.3.8 窗口控制中的重发控制

在使用窗口控制中, 出现丢包一般分为两种情况:

① 确认应答未能返回的情况。在这种情况下,数据已经到达对端,是不需要再进行重发的,如下图:
在这里插入图片描述
② 某个报文段丢失的情况。接收主机如果收到一个自己应该接收的序列号以外的数据时,会针对当前为止收到数据返回确认应答。如下图所示,当某一报文段丢失后,发送端会一直收到序号为1001的确认应答,因此,在窗口比较大,又出现报文段丢失的情况下,同一个序列号的确认应答将会被重复不断地返回。而发送端主机如果连续3次收到同一个确认应答,就会将其对应的数据进行重发。这种机制比之前提到的超时管理更加高效,因此也被称为高速重发控制。
在这里插入图片描述

5.4 网络层中的 IP 协议

  • IP(IPv4、IPv6)相当于 OSI 参考模型中的第3层——网络层。网络层的主要作用是“实现终端节点之间的通信”。这种终端节点之间的通信也叫“点对点通信”。
  • 网络的下一层——数据链路层的主要作用是在互连同一种数据链路的节点之间进行包传递。而一旦跨越多种数据链路,就需要借助网络层。网络层可以跨越不同的数据链路,即使是在不同的数据链路上也能实现两端节点之间的数据包传输。
  • IP 大致分为三大作用模块,它们是 IP 寻址、路由(最终节点为止的转发)以及 IP 分包与组包。

5.4.1 IP 地址

1.1 IP 地址概述

  • 在计算机通信中,为了识别通信对端,必须要有一个类似于地址的识别码进行标识。在数据链路中的 MAC 地址正是用来标识同一个链路中不同计算机的一种识别码。
  • 作为网络层的 IP ,也有这种地址信息,一般叫做 IP 地址。IP 地址用于在“连接到网络中的所有主机中识别出进行通信的目标地址”。因此,在 TCP/IP 通信中所有主机或路由器必须设定自己的 IP 地址。
  • 不论一台主机与哪种数据链路连接,其 IP 地址的形式都保持不变。
  • IP 地址(IPv4 地址)由32位正整数来表示。IP 地址在计算机内部以二进制方式被处理。然而,由于我们并不习惯于采用二进制方式,我们将32位的 IP 地址以每8位为一组,分成4组,每组以 “.” 隔开,再将每组数转换成十进制数。如下:

在这里插入图片描述

1.2 IP 地址由网络和主机两部分标识组成

  • 如下图,网络标识在数据链路的每个段配置不同的值。网络标识必须保证相互连接的每个段的地址不相重复。而相同段内相连的主机必须有相同的网络地址。IP 地址的“主机标识”则不允许在同一个网段内重复出现。由此,可以通过设置网络地址和主机地址,在相互连接的整个网络中保证每台主机的 IP 地址都不会相互重叠。即 IP 地址具有了唯一性。
    在这里插入图片描述
    如下图,IP 包被转发到途中某个路由器时,正是利用目标 IP 地址的网络标识进行路由。因为即使不看主机标识,只要一见到网络标识就能判断出是否为该网段内的主机在这里插入图片描述
    1.3 IP 地址的分类

  • IP 地址分为四个级别,分别为A类、B类、C类、D类。它根据 IP 地址中从第 1 位到第 4 位的比特列对其网络标识和主机标识进行区分。

  • A 类 IP 地址是首位以 “0” 开头的地址。从第 1 位到第 8 位是它的网络标识。用十进制表示的话,0.0.0.0~127.0.0.0 是 A 类的网络地址。A 类地址的后 24 位相当于主机标识。因此,一个网段内可容纳的主机地址上限为16,777,214个。

  • B 类 IP 地址是前两位 “10” 的地址。从第 1 位到第 16 位是它的网络标识。用十进制表示的话,128.0.0.0~191.255.0.0 是 B 类的网络地址。B 类地址的后 16 位相当于主机标识。因此,一个网段内可容纳的主机地址上限为65,534个。

  • C 类 IP 地址是前三位为 “110” 的地址。从第 1 位到第 24 位是它的网络标识。用十进制表示的话,192.0.0.0~223.255.255.0 是 C 类的网络地址。C 类地址的后 8 位相当于主机标识。因此,一个网段内可容纳的主机地址上限为254个。

  • D 类 IP 地址是前四位为 “1110” 的地址。从第 1 位到第 32 位是它的网络标识。用十进制表示的话,224.0.0.0~239.255.255.255 是 D 类的网络地址。D 类地址没有主机标识,常用于多播。

  • 在分配 IP 地址时关于主机标识有一点需要注意。即要用比特位表示主机地址时,不可以全部为 0 或全部为 1。因为全部为 0 只有在表示对应的网络地址或 IP 地址不可以获知的情况下才使用。而全部为 1 的主机通常作为广播地址。因此,在分配过程中,应该去掉这两种情况。这也是为什么 C 类地址每个网段最多只能有 254( 28 - 2 = 254)个主机地址的原因。

1.4 广播地址

  • 广播地址用于在同一个链路中相互连接的主机之间发送数据包。将 IP 地址中的主机地址部分全部设置为 1,就成了广播地址。
  • 广播分为本地广播和直接广播两种。在本网络内的广播叫做本地广播;在不同网络之间的广播叫做直接广播。

1.5 IP 多播

  • 多播用于将包发送给特定组内的所有主机。由于其直接使用 IP 地址,因此也不存在可靠传输。
  • 相比于广播,多播既可以穿透路由器,又可以实现只给那些必要的组发送数据包。请看下图:
    在这里插入图片描述
  • IP 多播
  • 多播使用 D 类地址。因此,如果从首位开始到第 4 位是 “1110”,就可以认为是多播地址。而剩下的 28 位可以成为多播的组编号。
  • 此外, 对于多播,所有的主机(路由器以外的主机和终端主机)必须属于 224.0.0.1 的组,所有的路由器必须属于 224.0.0.2 的组。

1.6 子网掩码

  • 现在一个 IP 地址的网络标识和主机标识已不再受限于该地址的类别,而是由一个叫做“子网掩码”的识别码通过子网网络地址细分出比 A 类、B 类、C 类更小粒度的网络。这种方式实际上就是将原来 A 类、B 类、C 类等分类中的主机地址部分用作子网地址,可以将原网络分为多个物理网络的一种机制。
  • 子网掩码用二进制方式表示的话,也是一个 32 位的数字。它对应 IP 地址网络标识部分的位全部为 “1”,对应 IP 地址主机标识的部分则全部为 “0”。由此,一个 IP 地址可以不再受限于自己的类别,而是可以用这样的子网掩码自由地定位自己的网络标识长度。当然,子网掩码必须是 IP 地址的首位开始连续的 “1”。
  • 对于子网掩码,目前有两种表示方式。第一种是,将 IP 地址与子网掩码的地址分别用两行来表示。以 172.20.100.52 的前 26 位是网络地址的情况为例,如下:
    在这里插入图片描述
    第二种表示方式是,在每个 IP 地址后面追加网络地址的位数用 “/ ” 隔开,如下:
    在这里插入图片描述

5.4.2. 路由

  • 发送数据包时所使用的地址是网络层的地址,即 IP 地址。然而仅仅有 IP 地址还不足以实现将数据包发送到对端目标地址,在数据发送过程中还需要类似于“指明路由器或主机”的信息,以便真正发往目标地址。保存这种信息的就是路由控制表。
  • 该路由控制表的形成方式有两种:一种是管理员手动设置,另一种是路由器与其他路由器相互交换信息时自动刷新。前者也叫做静态路由控制,而后者叫做动态路由控制。
  • IP 协议始终认为路由表是正确的。然后,IP 本身并没有定义制作路由控制表的协议。即 IP 没有制作路由控制表的机制。该表示由一个叫做“路由协议”的协议制作而成。

2.1 IP 地址与路由控制

  • IP 地址的网络地址部分用于进行路由控制。
  • 路由控制表中记录着网络地址与下一步应该发送至路由器的地址。
  • 在发送 IP 包时,首先要确定 IP 包首部中的目标地址,再从路由控制表中找到与该地址具有相同网络地址的记录,根据该记录将 IP 包转发给相应的下一个路由器。如果路由控制表中存在多条相同网络地址的记录,就选择一个最为吻合的网络地址。
    在这里插入图片描述
    路由控制表与 IP 包发送

5.4.3. IP 分包与组包

  • 每种数据链路的传输单元(MTU)都不尽相同,因为每个不同类型的数据链路的使用目的不同。使用目的不同,可承载的 MTU 也就不同。
  • 任何一台主机都有必要对 IP 分片进行相应的处理。分片往往在网络上遇到比较大的报文无法一下子发送出去时才会进行处理。
  • 经过分片之后的 IP 数据报在被重组的时候,只能由目标主机进行。路由器虽然做分片但不会进行重组。

3.1 路径 MTU 发现

  • 分片机制也有它的不足。如路由器的处理负荷加重之类。因此,只要允许,是不希望由路由器进行 IP 数据包的分片处理的。
  • 为了应对分片机制的不足,“路径 MTU 发现” 技术应运而生。路径 MTU 指的是,从发送端主机到接收端主机之间不需要分片是*** MTU 的大小。即路径中存在的所有数据链路中最小的 MTU 。
  • 进行路径 MTU 发现,就可以避免在中途的路由器上进行分片处理,也可以在 TCP 中发送更大的包。

5.4.4. IPv6

  • IPv6(IP version 6)是为了根本解决 IPv4 地址耗尽的问题而被标准化的网际协议。IPv4 的地址长度为 4 个 8 位字节,即 32 比特。而 IPv6 的地址长度则是原来的 4 倍,即 128 比特,一般写成 8 个 16 位字节。

4.1 IPv6 的特点

  • IP 得知的扩大与路由控制表的聚合。
  • 性能提升。包首部长度采用固定的值(40字节),不再采用首部检验码。简化首部结构,减轻路由器负担。路由器不再做分片处理。
  • 支持即插即用功能。即使没有DHCP服务器也可以实现自动分配 IP 地址。
  • 采用认证与加密功能。应对伪造 IP 地址的网络安全功能以及防止线路窃听的功能。
  • 多播、Mobile IP 成为扩展功能。

4.2 IPv6 中 IP 地址的标记方法

  • 一般人们将 128 比特 IP 地址以每 16 比特为一组,每组用冒号(“:”)隔开进行标记。
  • 而且如果出现连续的 0 时还可以将这些 0 省略,并用两个冒号(“::”)隔开。但是,一个 IP 地址中只允许出现一次两个连续的冒号。

4.3 IPv6 地址的结构

  • IPv6 类似 IPv4,也是通过 IP 地址的前几位标识 IP 地址的种类。

  • 在互联网通信中,使用一种全局的单播地址。它是互联网中唯一的一个地址,不需要正式分配 IP 地址。
    在这里插入图片描述
    4.4 全局单播地址

  • 全局单播地址是指世界上唯一的一个地址。它是互联网通信以及各个域内部通信中最为常用的一个 IPv6 地址。

  • 格式如下图所示,现在 IPv6 的网络中所使用的格式为,n = 48,m = 16 以及 128 - n - m = 64。即前 64 比特为网络标识,后 64 比特为主机标识。
    在这里插入图片描述
    全局单播地址

4.5 链路本地单播地址

  • 链路本地单播地址是指在同一个数据链路内唯一的地址。它用于不经过路由器,在同一个链路中的通信。通常接口 ID 保存 64 比特版的 MAC 地址。
    在这里插入图片描述
    链路本地单播地址

4.6 唯一本地地址

  • 唯一本地地址是不进行互联网通信时所用的地址。
  • 唯一本地地址虽然不会与互联网连接,但是也会尽可能地随机生成一个唯一的全局 ID。
  • L 通常被置为 1
  • 全局 ID 的值随机决定
  • 子网 ID 是指该域子网地址
  • 接口 ID 即为接口的 ID
    在这里插入图片描述
    唯一本地地址

4.7 IPv6 分段处理

  • IPv6 的分片处理只在作为起点的发送端主机上进行,路由器不参与分片。
  • IPv6 中最小 MTU 为 1280 字节,因此,在嵌入式系统中对于那些有一定系统资源限制的设备来说,不需要进行“路径 MTU 发现”,而是在发送 IP 包时直接以 1280 字节为单位分片送出。

4.8 IP 首部(暂略)

5.4.5. IP 协议相关技术

  • IP 旨在让最终目标主机收到数据包,但是在这一过程中仅仅有 IP 是无法实现通信的。必须还有能够解析主机名称和 MAC 地址的功能,以及数据包在发送过程中异常情况处理的功能。

5.1 DNS

  • 我们平常在访问某个网站时不适用 IP 地址,而是用一串由罗马字和点号组成的字符串。而一般用户在使用 TCP/IP 进行通信时也不使用 IP 地址。能够这样做是因为有了 DNS (Domain Name System)功能的支持。DNS 可以将那串字符串自动转换为具体的 IP 地址。
  • 这种 DNS 不仅适用于 IPv4,还适用于 IPv6。

5.2 ARP

  • 只要确定了 IP 地址,就可以向这个目标地址发送 IP 数据报。然而,在底层数据链路层,进行实际通信时却有必要了解每个 IP 地址所对应的 MAC 地址。
  • ARP 是一种解决地址问题的协议。以目标 IP 地址为线索,用来定位下一个应该接收数据分包的网络设备对应的 MAC 地址。不过 ARP 只适用于 IPv4,不能用于 IPv6。IPv6 中可以用 ICMPv6 替代 ARP 发送邻居探索消息。
  • RARP 是将 ARP 反过来,从 MAC 地址定位 IP 地址的一种协议。

5.3 ICMP

  • ICMP 的主要功能包括,确认 IP 包是否成功送达目标地址,通知在发送过程当中 IP 包被废弃的具体原因,改善网络设置等。
  • IPv4 中 ICMP 仅作为一个辅助作用支持 IPv4。也就是说,在 IPv4 时期,即使没有 ICMP,仍然可以实现 IP 通信。然而,在 IPv6 中,ICMP 的作用被扩大,如果没有 ICMPv6,IPv6 就无法进行正常通信。

5.4 DHCP

  • 如果逐一为每一台主机设置 IP 地址会是非常繁琐的事情。特别是在移动使用笔记本电脑、只能终端以及平板电脑等设备时,每移动到一个新的地方,都要重新设置 IP 地址。
  • 于是,为了实现自动设置 IP 地址、统一管理 IP 地址分配,就产生了 DHCP(Dynamic Host Configuration Protocol)协议。有了 DHCP,计算机只要连接到网络,就可以进行 TCP/IP 通信。也就是说,DHCP 让即插即用变得可能。
  • DHCP 不仅在 IPv4 中,在 IPv6 中也可以使用。

5.5 NAT

  • NAT(Network Address Translator)是用于在本地网络中使用私有地址,在连接互联网时转而使用全局 IP 地址的技术。
  • 除转换 IP 地址外,还出现了可以转换 TCP、UDP 端口号的 NAPT(Network Address Ports Translator)技术,由此可以实现用一个全局 IP 地址与多个主机的通信。
  • NAT(NAPT)实际上是为正在面临地址枯竭的 IPv4 而开发的技术。不过,在 IPv6 中为了提高网络安全也在使用 NAT,在 IPv4 和 IPv6 之间的相互通信当中常常使用 NAT-PT。

5.6 IP 隧道

在这里插入图片描述

夹着 IPv4 网络的两个 IPv6 网络

  • 如上图的网络环境中,网络 A 与网络 B 之间无法直接进行通信,为了让它们之间正常通信,这时必须得采用 IP 隧道的功能。
  • IP 隧道可以将那些从网络 A 发过来的 IPv6 的包统合为一个数据,再为之追加一个 IPv4 的首部以后转发给网络 C。
  • 一般情况下,紧接着 IP 首部的是 TCP 或 UDP 的首部。然而,现在的应用当中“ IP 首部的后面还是 IP 首部”或者“ IP 首部的后面是 IPv6 的首部”等情况与日俱增。这种在网络层的首部后面追加网络层首部的通信方法就叫做“ IP 隧道”。
    5.5 HTTP 和 HTTPS

5.5.1 HTTP

5.5.1.1 概述

http协议是基于TCP/IP协议之上的应用层协议。

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。我们称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)。

尽管TCP/IP协议是互联网上最流行的应用,HTTP协议中,并没有规定必须使用它或它支持的层。事实上,HTTP可以在任何互联网协议上,或其他网络上实现。HTTP假定其下层协议提供可靠的传输。因此,任何能够提供这种保证的协议都可以被其使用。因此也就是其在TCP/IP协议族使用TCP作为其传输层。

通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息。

5.5.1.2 URL

超文本传输协议(HTTP)的统一资源定位符将从因特网获取信息的五个基本元素包括在一个简单的地址中:

  • 传送协议。
  • 层级URL标记符号(为[//],固定不变)
  • 访问资源需要的凭证信息(可省略)
  • 服务器。(通常为域名,有时为IP地址)
  • 端口号。(以数字方式表示,若为HTTP的默认值“:80”可省略)
  • 路径。(以“/”字符区别路径中的每一个目录名称)
  • 查询。(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
  • 片段。以“#”字符为起点

以http://www.luffycity.com:80/news/index.html?id=250&page=1 为例, 其中:

http,是协议;

www.luffycity.com,是服务器;

80,是服务器上的默认网络端口号,默认不显示;

/news/index.html,是路径(URI:直接定位到对应的资源);

?id=250&page=1,是查询。

大多数网页浏览器不要求用户输入网页中“http://”的部分,因为绝大多数网页内容是超文本传输协议文件。同样,“80”是超文本传输协议文件的常用端口号,因此一般也不必写明。一般来说用户只要键入统一资源定位符的一部分(www.luffycity.com:80/news/index.html?id=250&page=1)就可以了。

由于超文本传输协议允许服务器将浏览器重定向到另一个网页地址,因此许多服务器允许用户省略网页地址中的部分,比如 www。从技术上来说这样省略后的网页地址实际上是一个不同的网页地址,浏览器本身无法决定这个新地址是否通,服务器必须完成重定向的任务。

5.5.1.3 HTTP状态码

所有HTTP响应的第一行都是状态行,依次是当前HTTP版本号,3位数字组成的状态代码,以及描述状态的短语,彼此由空格分隔。

状态代码的第一个数字代表当前响应的类型:

  • 1xx消息——请求已被服务器接收,继续处理
  • 2xx成功——请求已成功被服务器接收、理解、并接受
  • 3xx重定向——需要后续操作才能完成这一请求
  • 4xx请求错误——请求含有词法错误或者无法被执行
  • 5xx服务器错误——服务器在处理某个正确请求时发生错误

虽然 RFC 2616 中已经推荐了描述状态的短语,例如"200 OK",“404 Not Found”,但是WEB开发者仍然能够自行决定采用何种短语,用以显示本地化的状态描述或者自定义信息。

5.5.1.4 HTTP请求方法

HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源:

  • GET
    向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
  • HEAD
    与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
  • POST
    向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。
  • PUT
    向指定资源位置上传其最新内容。
  • DELETE
    请求服务器删除Request-URI所标识的资源。
  • TRACE
    回显服务器收到的请求,主要用于测试或诊断。
  • OPTIONS
    这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用’*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。
  • CONNECT
    HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。

注意事项:

  1. 方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed),当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
  2. HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。当然,所有的方法支持的实现都应当匹配下述的方法各自的语义定义。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。例如PATCH(由 RFC 5789 指定的方法)用于将局部修改应用到资源。

请求方式: get与post请求(通过form表单我们自己写写看)

  • GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456.(请求头里面那个content-type做的这种参数形式,后面讲) POST方法是把提交的数据放在HTTP包的请求体中.
  • GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
  • GET与POST请求在服务端获取请求数据方式不同,就是我们自己在服务端取请求数据的时候的方式不同了,这句废话昂。

5.5.1.5 HTTP的执行过程

  1. 客户端连接到Web服务器
  2. 发送HTTP请求
  3. 服务器接受请求并返回HTTP响应
  4. 释放连接TCP连接
  5. 客户端浏览器解析HTML内容

例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:

  1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
  3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
  4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
  5. 释放 TCP连接;
  6. 浏览器将该 html 文本并显示内容;

5.5.2 HTTPS

HTTPS是一种通过计算机网络进行安全通信的传输协议,经由HTTP进行通信,利用SSL/TLS建立全信道,加密数据包。HTTPS使用的主要目的是 提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性。

5.5.2.1特点:
  • 内容加密:采用混合加密技术,中间者无法直接查看明文内容
  • 验证身份:通过证书认证客户端访问的是自己的服务器
  • 保护数据完整性:防止传输的内容被中间人冒充或者篡改
  • 收方能够证实发送方的真实身份;
  • 发送方事后不能否认所发送过的报文;
  • 收方或非法者不能伪造、篡改报文。
5.5.2.2加密技术

混合加密:结合非对称加密和对称加密技术。客户端使用对称加密生成密钥对传输数据进行加密,然后使用非对称加密的公钥再对秘钥进行加密,所以网络上传输的数据是被秘钥加密的密文和用公钥加密后的秘密秘钥,因此即使被黑客截取,由于没有私钥,无法获取到加密明文的秘钥,便无法获取到明文数据。

数字摘要:通过单向hash函数对原文进行哈希,将需加密的明文“摘要”成一串固定长度(如128bit)的密文,不同的明文摘要成的密文其结果总是不相同,同样的明文其摘要必定一致,并且即使知道了摘要也不能反推出明文。

数字签名技术:数字签名建立在公钥加密体制基础上,是公钥加密技术的另一类应用。它把公钥加密技术和数字摘要结合起来,形成了实用的数字签名技术。

5.5.2.3 HTTP与HTTPS有什么区别?

HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTPS和HTTP的区别主要如下:

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

5.5.2.4 HTTPS的缺点

虽然说HTTPS有很大的优势,但其相对来说,还是存在不足之处的:

(1)HTTPS协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;

(2)HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;

(3)SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。

(4)SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。

(5)HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。

5.5.2.5 http切换到HTTPS

如果需要将网站从http切换到https到底该如何实现呢?

 这里需要将页面中所有的链接,例如js,css,图片等等链接都由http改为https。例如:http://www.baidu.com改为https://www.baidu.com

BTW,这里虽然将http切换为了https,还是建议保留http。所以我们在切换的时候可以做http和https的兼容,具体实现方式是,去掉页面链接中的http头部,这样可以自动匹配http头和https头。例如:将http://www.baidu.com改为//www.baidu.com。然后当用户从http的入口进入访问页面时,页面就是http,如果用户是从https的入口进入访问页面,页面即使https的。

6. 高并发 / 多线程 / 分布式

面经:

1、java线程池的实现,java线程池常用的配置,线程池的拒绝策略
https://www.cnblogs.com/owenma/p/8557074.html

2、线程的执行结果如何获取?
https://www.cnblogs.com/dafanjoy/p/14505058.html

3、线程之间是如何通信的?
https://zhuanlan.zhihu.com/p/129374075

6.1 概念

6.1.1 并发和并行的区别

由于机器的不同,当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent)。

当系统有一个以上CPU时,则线程的操作有可能非并发.当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

并行:指的是 同一时刻有多个任务正在运行
请添加图片描述
并发:指的是 同一时刻有一个任务正在运行
在这里插入图片描述

6.1.2 阻塞与非阻塞

阻塞与非阻塞的重点在于进/线程等待消息时候的行为,也就是在等待消息的时候,当前进/线程是挂起状态,还是非挂起状态。

阻塞:调用在发出去后,在消息返回之前,当前进/线程会被挂起,直到有消息返回,当前进/线程才会被激活;

非阻塞:调用在发出去后,不会阻塞当前进/线程,而会立即返回。

6.1.3 同步与异步

同步:当一个同步调用发出去后,调用者要一直等待调用结果的返回后,才能进行后续的操作。

异步:当一个异步调用发出去后,调用者不用管被调用方法是否完成,都会继续执行后面的代码。 异步调用,要想获得结果,一般有两种方式:

主动轮询异步调用的结果;

被调用方通过callback来通知调用方调用结果;

比如,在超市购物,如果一件物品没了,你得等仓库人员跟你调货,直到仓库人员跟你把货物送过来,你才能继续去收银台付款,这就类似同步调用。而异步调用了,就像网购,你在网上付款下单后,什么事就不用管了,该干嘛就干嘛去了,当货物到达后你收到通知去取就好。

6.1.4 临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

6.1.5 上下文切换

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。

概括来讲:

6.2 并发编程的优缺点

优点:

  • 充分利用多核CPU的计算能力
  • 方便进行业务拆分,提升系统并发能力和性能

缺点:

  • 内存泄漏

  • 上下文切换
    任务从保存到再加载就是一次上下文切换。
    每次切换时,需要保存当前的状态,以便能够进行恢复先前的状态,而这个切换时非常损耗性能

    减少上下文切换的解决方案:

    • 无锁并发编程:可以参照concurrentHashMap锁分段的思想,不同的线程处理不同段的数据,这样在多线程竞争的条件下,可以减少上下文切换的时间。
    • CAS算法:利用Atomic下使用CAS算法来更新数据,使用了乐观锁,可以有效的减少一部分不必要的锁竞争带来的上下文切换。
    • 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多的线程,这样会造成大量的线程都处于等待状态。
    • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
      由于上下文切换也是个相对比较耗时的操作,所以在"java并发编程的艺术"一书中有过一个实验,并发累加未必会比串行累加速度要快。 可以使用Lmbench3测量上下文切换的时长 vmstat测量上下文切换次数
  • 线程安全,死锁等问题
    多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的情况,一旦产生死锁就会造成系统功能不可用。
    通常可以用如下方式避免死锁的情况:

    • 避免一个线程同时获得多个锁;
    • 避免一个线程在锁内部占有多个资源,尽量保证每个锁只占用一个资源;
    • 尝试使用定时锁,使用lock.tryLock(timeOut),当超时等待时当前线程不会阻塞;
    • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

6.3 并发编程的特性

并发编程:可见性,原子性,有序性

多线程共享变量的底层可见性的研究,使用volatile关键字,volatile保证可见性和有序性,但不保证原子性,保证原子性需要用synchronized关键字

    package net.yto.app;
    
    /**
     * @Author easychill
     * @Date 2020/3/4 11:02
     * @Version 1.0
     */
    public class Test {
        private static volatile boolean initFlag = false;
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(()->{
                System.out.println("get data");
                while (!initFlag){}
                System.out.println("success");
            });
            thread.start();
            Thread.sleep(2000);
            Thread thread1 = new Thread(()->{
                getdata();
            });
            thread1.run();
        }
        public static void getdata(){
            System.out.println("data");
            System.out.println("get data end");
            initFlag = true;
        }
    }

上面的代码执行过程:

  • 线程1进入
  • 从主内存中读取(read)数据
  • 然后加载(load)到我们的工作内存中,(每一个线程对于主内存的数据,都会拷贝一份数据,到自己的工作内存)
  • 然后使用(use)判断以后一直进行循环,
  • 暂停两秒
  • 然后进行第二个线程,也是读取,加载,使用
  • 使用的过程,输出两条语句后,改为了true,当initFlag变量有volatite这个关键字修饰的时候,就会执行赋值(assign)操作,并且返回给线程2的工作内存
  • 然后将改变的变量重新的回写到主内存,这时,initFlag的值发生变化,线程1感知,所以,while死循环就会停止,这都是volatite致使的。
    注意下图中:加锁的位置,加锁粒度越小,效率越高。(锁里面的步骤越少)

在这里插入图片描述
在这里插入图片描述
当我们没有使用volatite关键字的时候,死循环还会继续,以下是含有volatite的输出,没有volatite的情况下则不会输出success,并且,程序不会停止

get data
data
get data end
success

volatile底层实现不是java实现的。
在这里插入图片描述

6.4 进程创建的四种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用Callable和Future创建线程
  4. 使用Executor框架创建线程池

6.5 线程的状态和生命周期

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态
在这里插入图片描述
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示 在这里插入图片描述

6.6 线程状态的基本操作

除了新建一个线程外,线程在生命周期内还有需要进行一些基本操作,而这些操作会成为线程间一种通信方式,比如使用中断(interrupted)方式通知实现线程间的交互等等,下面就将具体说说这些操作。

6.6.1 interrupted

中断可以理解为线程的一个标志位,它表示了一个运行中的线程是否被其他线程进行了中断操作。中断好比其他线程对该线程打了一个招呼。其他线程可以调用该线程的interrupt()方法对其进行中断操作,同时该线程可以调用

isInterrupted()来感知其他线程对其自身的中断操作,从而做出响应。另外,同样可以调用Thread的静态方法

interrupted()对当前线程进行中断操作,该方法会清除中断标志位。需要注意的是,当抛出InterruptedException时候,会清除中断标志位,也就是说在调用isInterrupted会返回false。

在这里插入图片描述
下面结合具体的实例来看一看

    public class InterruptDemo {
        public static void main(String[] args) throws InterruptedException {
            //sleepThread睡眠1000ms
            final Thread sleepThread = new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    super.run();
                }
            };
            //busyThread一直执行死循环
            Thread busyThread = new Thread() {
                @Override
                public void run() {
                    while (true) ;
                }
            };
            sleepThread.start();
            busyThread.start();
            sleepThread.interrupt();
            busyThread.interrupt();
            while (sleepThread.isInterrupted()) ;
            System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());
            System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());
        }
    }

输出结果

    sleepThread isInterrupted: false
    busyThread isInterrupted: true
    java.lang.InterruptedException: sleep interrupted
    	at java.lang.Thread.sleep(Native Method)
    	at com.jourwon.test.InterruptDemo$1.run(InterruptDemo.java:17)

开启了两个线程分别为sleepThread和BusyThread, sleepThread睡眠1s,BusyThread执行死循环。然后分别对着两个线程进行中断操作,可以看出sleepThread抛出InterruptedException后清除标志位,而busyThread就不会清除标志位。

另外,同样可以通过中断的方式实现线程间的简单交互, while (sleepThread.isInterrupted()) 表示在Main线程中会持续监测sleepThread线程,一旦sleepThread的中断标志位清零,即sleepThread.isInterrupted()返回为false时才会继续Main线程才会继续往下执行。因此,中断操作可以看做线程间一种简便的交互方式。一般在结束线程时通过中断标志位或者标志位的方式可以有机会去清理资源,相对于武断而直接的结束线程,这种方式要优雅和安全

6.6.2 join

join方法可以看做是线程间协作的一种方式,很多时候,一个线程的输入可能非常依赖于另一个线程的输出,这就像两个好基友,一个基友先走在前面突然看见另一个基友落在后面了,这个时候他就会在原处等一等这个基友,等基友赶上来后,就两人携手并进。其实线程间的这种协作方式也符合现实生活。在软件开发的过程中,从客户那里获取需求后,需要经过需求分析师进行需求分解后,这个时候产品,开发才会继续跟进。如果一个线程实例A执行了threadB.join(),其含义是:当前线程A会等待threadB线程终止后threadA才会继续执行。关于join方法一共提供如下这些方法:
在这里插入图片描述
Thread类除了提供join()方法外,另外还提供了超时等待的方法,如果线程threadB在等待的时间内还没有结束的话,threadA会在超时之后继续执行。join方法源码关键是:

while (isAlive()) {
    wait(0);
}

可以看出来当前等待对象threadA会一直阻塞,直到被等待对象threadB结束后即isAlive()返回false的时候才会结束while循环,当threadB退出时会调用notifyAll()方法通知所有的等待线程。下面用一个具体的例子来说说join方法的使用:


    public class JoinDemo {
        public static void main(String[] args) {
            Thread previousThread = Thread.currentThread();
            for (int i = 1; i <= 10; i++) {
                Thread curThread = new JoinThread(previousThread);
                curThread.start();
                previousThread = curThread;
            }
        }
    
        static class JoinThread extends Thread {
            private Thread thread;
    
            public JoinThread(Thread thread) {
                this.thread = thread;
            }
    
            @Override
            public void run() {
                try {
                    thread.join();
                    System.out.println(thread.getName() + " terminated.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

输出结果为:

main terminated.
Thread-0 terminated.
Thread-1 terminated.
Thread-2 terminated.
Thread-3 terminated.
Thread-4 terminated.
Thread-5 terminated.
Thread-6 terminated.
Thread-7 terminated.
Thread-8 terminated.

在上面的例子中一个创建了10个线程,每个线程都会等待前一个线程结束才会继续运行。可以通俗的理解成接力,前一个线程将接力棒传给下一个线程,然后又传给下一个线程…

6.6.3 sleep

public static native void sleep(long millis)方法显然是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。sleep方法经常拿来与Object.wait()方法进行比价,这也是面试经常被问的地方。

sleep() VS wait()

两者主要的区别:

sleep()方法是Thread的静态方法,而wait是Object实例方法

wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。而sleep()方法没有这个限制可以在任何地方使用。另外,wait()方法会释放占有的对象锁,使得该线程进入等待池中,等待下一次获取资源。而sleep()方法只是会让出CPU并不会释放掉对象锁;

sleep()方法在休眠时间达到后,如果再次获得CPU时间片就会继续执行,而wait()方法必须等待Object.notift/Object.notifyAll通知后,才会离开等待池,并且再次获得CPU时间片才会继续执行。

6.6.4 yield

public static native void yield()这是一个静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。什么是线程优先级了?下面就来具体聊一聊。

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当前时间片用完后就会发生线程调度,并等待下次分配。线程分配到的时间多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要或多或少分配一些处理器资源的线程属性。

在Java程序中,通过一个整型成员变量Priority来控制优先级,优先级的范围从1~10.在构建线程的时候可以通过setPriority(int)方法进行设置,默认优先级为5,优先级高的线程相较于优先级低的线程优先获得处理器时间片。需要注意的是在不同JVM以及操作系统上,线程规划存在差异,有些操作系统甚至会忽略线程优先级的设定。

另外需要注意的是,sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。

6.7 线程优先级

理论上来说系统会根据优先级来决定首先使哪个线程进入运行状态。当 CPU 比较闲的时候,设置线程优先级几乎不会有任何作用,而且很多操作系统压根不会理会你设置的线程优先级,所以不要让业务过度依赖于线程的优先级。

另外,线程优先级具有继承特性比如 A 线程启动 B 线程,则 B 线程的优先级和 A 是一样的。线程优先级还具有随机性 也就是说线程优先级高的不一定每一次都先执行完。

Thread 类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数 1),Thread.NORM_PRIORITY(常数 5),Thread.MAX_PRIORITY(常数 10)。其中每个线程的优先级都在1到10 之间,1的优先级为最低,10的优先级为最高,在默认情况下优先级都是Thread.NORM_PRIORITY(常数 5)。

一般情况下,不会对线程设定优先级别,更不会让某些业务严重地依赖线程的优先级别,比如权重,借助优先级设定某个任务的权重,这种方式是不可取的,一般定义线程的时候使用默认的优先级就好了。

相关方法:

    public final void setPriority(int newPriority) //为线程设定优先级
    public final int getPriority() //获取线程的优先级

//设置线程优先级方法源码: 

    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        //线程游戏优先级不能小于 1 也不能大于 10,否则会抛出异常
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        //如果指定的线程优先级大于该线程所在线程组的最大优先级,那么该线程的优先级将设为线程组的最大优先级
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

6.8 进程与线程的区别

线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

从 JVM 角度说进程和线程之间的关系(重要)

下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。
在这里插入图片描述
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。

程序计数器为什么是私有的?

程序计数器主要有下面两个作用:

字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。

在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。

虚拟机栈和本地方法栈为什么是私有的?

虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。

本地方法栈:和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

一句话简单了解堆和方法区

堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

Java中的多线程

Java程序的进程里有几个线程:主线程,垃圾回收线程(后台线程)等

在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

Java支持多线程,当Java程序执行main方法的时候,就是在执行一个名字叫做main的线程,可以在main方法执行时,开启多个线程A,B,C,多个线程 main,A,B,C同时执行,相互抢夺CPU,Thread类是java.lang包下的一个常用类,每一个Thread类的对象,就代表一个处于某种状态的线程

public class MultiThread {
	public static void main(String[] args) {
		// 获取 Java 线程管理 MXBean
		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
		// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
		ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
		// 遍历线程信息,仅打印线程 ID 和线程名称信息
		for (ThreadInfo threadInfo : threadInfos) {
			System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
		}
	}
}


//以下为输出
[6] Monitor Ctrl-Break //监听线程转储或“线程堆栈跟踪”的线程
[5] Attach Listener //负责接收到外部的命令,而对该命令进行执行的并且把结果返回给发送者
[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
[3] Finalizer //在垃圾收集前,调用对象 finalize 方法的线程
[2] Reference Handler //用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收的线程
[1] main //main 线程,程序入口

6.9 守护线程和用户线程

守护线程和用户线程简介:

用户 (User) 线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

守护 (Daemon) 线程:运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作

main 函数所在的线程就是一个用户线程啊,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。

那么守护线程和用户线程有什么区别呢?

比较明显的区别之一是用户线程结束,JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。

注意事项:

  • setDaemon(true)必须在start()方法前执行,否则会抛出 IllegalThreadStateException 异常
  • 在守护线程中产生的新线程也是守护线程
  • 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
  • 守护 (Daemon) 线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作,所以守护 (Daemon) 线程中的 finally 语句块可能无法被执行。

6.10 线程死锁

认识线程死锁

死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
在这里插入图片描述

形成死锁的四个必要条件:

  • 互斥条件:线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放
  • 请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  • 循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞

如何避免线程死锁

我们只要破坏产生死锁的四个条件中的其中一个就可以了。

  • 破坏互斥条件
    • 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
  • 破坏请求与保持条件
    • 一次性申请所有的资源。
  • 破坏不剥夺条件
    • 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • 破坏循环等待条件
    • 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

6.11 分布式

6.11.1 什么是分布式系统

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。

其目的是利用更多的机器,处理更多的数据。

6.11.2 衡量是否转变为分布式系统

只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。

因为,分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。

6.11.3 分布式系统设计思想

一个简单计算机需要的成分:计算和存储

分布式系统分为分布式计算(computation)与分布式存储(storage ),它也只是将计算机的基本能力分布到多个节点

那么一个完整的任务,怎么分发给不同的计算机节点呢?分而治之,即分片(partition) !

对于计算,那么就是对计算任务进行切换,每个节点算一些,最终汇总就行了,这就是MapReduce的思想;对于存储,更好理解一下,每个节点存一部分数据就行了。当数据规模变大的时候,Partition是唯一的选择,同时也会带来一些好处:

(1)提升性能和并发,操作被分发到不同的分片,相互独立

(2)提升系统的可用性,即使部分分片不能用,其他分片不会受到影响

理想的情况下,有分片就行了,但事实的情况却不大理想。原因在于,分布式系统中有大量的节点,且通过网络通信。单个节点的故障(进程crash、断电、磁盘损坏)是个小概率事件,但整个系统的故障率会随节点的增加而指数级增加,网络通信也可能出现断网、高延迟的情况。在这种一定会出现的“异常”情况下,分布式系统还是需要继续稳定的对外提供服务,即需要较强的容错性。最简单的办法,就是冗余或者复制集(Replication),即多个节点负责同一个任务,最为常见的就是分布式存储中,多个节点复杂存储同一份数据,以此增强可用性与可靠性。同时,Replication也会带来性能的提升,比如数据的locality可以减少用户的等待时间。

在这里插入图片描述

6.11.4 分布式系统面临的挑战

第一,异构的机器与网络:

分布式系统中的机器,配置不一样,其上运行的服务也可能由不同的语言、架构实现,因此处理能力也不一样;节点间通过网络连接,而不同网络运营商提供的网络的带宽、延时、丢包率又不一样。怎么保证大家齐头并进,共同完成目标,这四个不小的挑战。

第二,普遍的节点故障:

虽然单个节点的故障概率较低,但节点数目达到一定规模,出故障的概率就变高了。分布式系统需要保证故障发生的时候,系统仍然是可用的,这就需要监控节点的状态,在节点故障的情况下将该节点负责的计算、存储任务转移到其他节点

第三,不可靠的网络:

节点间通过网络通信,而网络是不可靠的。可能的网络问题包括:网络分割、延时、丢包、乱序。

相比单机过程调用,网络通信最让人头疼的是超时:节点A向节点B发出请求,在约定的时间内没有收到节点B的响应,那么B是否处理了请求,这个是不确定的,这个不确定会带来诸多问题,最简单的,是否要重试请求,节点B会不会多次处理同一个请求。

总而言之,分布式的挑战来自不确定性,不确定计算机什么时候crash、断电,不确定磁盘什么时候损坏,不确定每次网络通信要延迟多久,也不确定通信对端是否处理了发送的消息。而分布式的规模放大了这个不确定性,不确定性是令人讨厌的,所以有诸多的分布式理论、协议来保证在这种不确定性的情况下,系统还能继续正常工作。

刘杰在《分布式系统原理介绍》中指出,处理这些异常的最佳原则是:在设计、推导、验证分布式系统的协议、流程时,最重要的工作之一就是思考在执行流程的每个步骤时一旦发生各种异常的情况下系统的处理方式及造成的影响。

6.11.5 一个分布式系统涉及到的技术点

假设这是一个对外提供服务的大型分布式系统,用户连接到系统,做一些操作,产生一些需要存储的数据,那么在这个过程中,会遇到哪些组件、理论与协议呢 ?

用户使用Web、APP、SDK,通过HTTP、TCP连接到系统。在分布式系统中,为了高并发、高可用,一般都是多个节点提供相同的服务。那么,第一个问题就是具体选择哪个节点来提供服务,这个就是负载均衡(load balance)。负载均衡的思想很简单,但使用非常广泛,在分布式系统、大型网站的方方面面都有使用,或者说,只要涉及到多个节点提供同质的服务,就需要负载均衡。

通过负载均衡找到一个节点,接下来就是真正处理用户的请求,请求有可能简单,也有可能很复杂。简单的请求,比如读取数据,那么很可能是有缓存的,即分布式缓存,如果缓存没有命中,那么需要去数据库拉取数据。对于复杂的请求,可能会调用到系统中其他的服务。

承上,假设服务A需要调用服务B的服务,首先两个节点需要通信,网络通信都是建立在TCP/IP协议的基础上,但是,每个应用都手写socket是一件冗杂、低效的事情,因此需要应用层的封装,因此有了HTTP、FTP等各种应用层协议。当系统愈加复杂,提供大量的http接口也是一件困难的事情。因此,有了更进一步的抽象,那就是RPC(remote produce call),是的远程调用就跟本地过程调用一样方便,屏蔽了网络通信等诸多细节,增加新的接口也更加方便。

一个请求可能包含诸多操作,即在服务A上做一些操作,然后在服务B上做另一些操作。比如简化版的网络购物,在订单服务上发货,在账户服务上扣款。这两个操作需要保证原子性,要么都成功,要么都不操作。这就涉及到分布式事务的问题,分布式事务是从应用层面保证一致性:某种守恒关系。

上面说道一个请求包含多个操作,其实就是涉及到多个服务,分布式系统中有大量的服务,每个服务又是多个节点组成。那么一个服务怎么找到另一个服务(的某个节点呢)?通信是需要地址的,怎么获取这个地址,最简单的办法就是配置文件写死,或者写入到数据库,但这些方法在节点数据巨大、节点动态增删的时候都不大方便,这个时候就需要服务注册与发现:提供服务的节点向一个协调中心注册自己的地址,使用服务的节点去协调中心拉取地址。

从上可以看见,协调中心提供了中心化的服务:以一组节点提供类似单点的服务,使用非常广泛,比如命令服务、分布式锁。协调中心最出名的就是chubby,zookeeper。

回到用户请求这个点,请求操作会产生一些数据、日志,通常为信息,其他一些系统可能会对这些消息感兴趣,比如个性化推荐、监控等,这里就抽象出了两个概念,消息的生产者与消费者。那么生产者怎么讲消息发送给消费者呢,RPC并不是一个很好的选择,因为RPC肯定得指定消息发给谁,但实际的情况是生产者并不清楚、也不关心谁会消费这个消息,这个时候消息队列就出马了。简单来说,生产者只用往消息队列里面发就行了,队列会将消息按主题(topic)分发给关注这个主题的消费者。消息队列起到了异步处理、应用解耦的作用。

上面提到,用户操作会产生一些数据,这些数据忠实记录了用户的操作习惯、喜好,是各行各业最宝贵的财富。比如各种推荐、广告投放、自动识别。这就催生了分布式计算平台,比如Hadoop,Storm等,用来处理这些海量的数据。

最后,用户的操作完成之后,用户的数据需要持久化,但数据量很大,大到按个节点无法存储,那么这个时候就需要分布式存储:将数据进行划分放在不同的节点上,同时,为了防止数据的丢失,每一份数据会保存多分。传统的关系型数据库是单点存储,为了在应用层透明的情况下分库分表,会引用额外的代理层。而对于NoSql,一般天然支持分布式。
在这里插入图片描述

  • 负载均衡
    • Nginx:高性能、高并发的web服务器;功能包括负载均衡、反向代理、静态内容缓存、访问控制;工作在应用层
    • LVS: Linux virtual server,基于集群技术和Linux操作系统实现一个高性能、高可用的服务器;工作在网络层
  • webserver
    • Java:Tomcat,Apache,Jboss
    • Python:gunicorn、uwsgi、twisted、webpy、tornado
  • service
    • SOA、微服务、spring boot,django
  • 容器
    • docker,kubernetes
  • cache
    • memcache、redis等
  • 协调中心
    • zookeeper、etcd等
    • zookeeper使用了Paxos协议Paxos是强一致性,高可用的去中心化分布式。zookeeper的使用场景非常广泛,之后细讲。
  • rpc框架
    • grpc、dubbo、brpc
    • dubbo是阿里开源的Java语言开发的高性能RPC框架,在阿里系的诸多架构中,都使用了dubbo + spring boot
  • 消息队列
    • kafka、rabbitMQ、rocketMQ、QSP
    • 消息队列的应用场景:异步处理、应用解耦、流量削锋和消息通讯
  • 实时数据平台
    • storm、akka
  • 离线数据平台
    • hadoop、spark
    • PS: apark、akka、kafka都是scala语言写的,看到这个语言还是很牛逼的
  • dbproxy
    • cobar也是阿里开源的,在阿里系中使用也非常广泛,是关系型数据库的sharding + replica 代理
  • db
    • mysql、oracle、MongoDB、HBase
  • 搜索
    • elasticsearch、solr
  • 日志
    • rsyslog、elk、flume

6.11.6 Nginx实现负载均衡

1、下载部署nginx环境
2、启动两个端口分别为8080 8081的项目。根路径映射不同的字符串。
3、启动ngnix

使用代理和轮询负载均衡机制
负载均衡
nginx配置文件详解

7. 缓存、中间件

面经:

多数都是mq的问题

1、rocket mq的模块组成?
在这里插入图片描述
2、rocketmq-常见问题总结(消息的顺序、重复、消费模式)
https://www.cnblogs.com/xuwc/p/9034352.html

rocket mq 知识点
rocket mq的面试题

7.1 缓存

一般而言,现在互联网应用(网站或App)的整体流程,可以概括如图所示,用户请求从界面(浏览器或App界面)到网络转发、应用服务再到存储(数据库或文件系统),然后返回到界面呈现内容。

随着互联网的普及,内容信息越来越复杂,用户数和访问量越来越大,我们的应用需要支撑更多的并发量,同时我们的应用服务器和数据库服务器所做的计算也越来越多。但是往往我们的应用服务器资源是有限的,且技术变革是缓慢的,数据库每秒能接受的请求次数也是有限的(或者文件的读写也是有限的),如何能够有效利用有限的资源来提供尽可能大的吞吐量?一个有效的办法就是引入缓存,打破标准流程,每个环节中请求可以从缓存中直接获取目标数据并返回,从而减少计算量,有效提升响应速度,让有限的资源服务更多的用户。

在这里插入图片描述

7.1.1 缓存特征

缓存也是一个数据模型对象,那么必然有它的一些特征:

7.1.1.1 命中率

命中率=返回正确结果数/请求缓存次数,命中率问题是缓存中的一个非常重要的问题,它是衡量缓存有效性的重要指标。命中率越高,表明缓存的使用率越高。

7.1.1.2最大元素(或最大空间)

缓存中可以存放的最大元素的数量,一旦缓存中元素数量超过这个值(或者缓存数据所占空间超过其最大支持空间),那么将会触发缓存启动清空策略根据不同的场景合理的设置最大元素值往往可以一定程度上提高缓存的命中率,从而更有效的时候缓存。

7.1.1.3清空策略

如上描述,缓存的存储空间有限制,当缓存空间被用满时,如何保证在稳定服务的同时有效提升命中率?这就由缓存清空策略来处理,设计适合自身数据特征的清空策略能有效提升命中率。常见的一般策略有:

  • FIFO(first in first out)

先进先出策略,最先进入缓存的数据在缓存空间不够的情况下(超出最大元素限制)会被优先被清除掉,以腾出新的空间接受新的数据。策略算法主要比较缓存元素的创建时间。在数据实效性要求场景下可选择该类策略,优先保障最新数据可用。

  • LFU(less frequently used)

最少使用策略,无论是否过期,根据元素的被使用次数判断,清除使用次数较少的元素释放空间。策略算法主要比较元素的hitCount(命中次数)。在保证高频数据有效性场景下,可选择这类策略。

  • LRU(least recently used)

最近最少使用策略,无论是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素释放空间。策略算法主要比较元素最近一次被get使用时间。在热点数据场景下较适用,优先保证热点数据的有效性。

除此之外,还有一些简单策略比如:

  • 根据过期时间判断,清理过期时间最长的元素;
  • 根据过期时间判断,清理最近要过期的元素;
  • 随机清理;
  • 根据关键字(或元素内容)长短清理等。

7.1.2 缓存介质

虽然从硬件介质上来看,无非就是内存和硬盘两种,但从技术上,可以分成内存、硬盘文件、数据库。

  • 内存:将缓存存储于内存中是最快的选择,无需额外的I/O开销,但是内存的缺点是没有持久化落地物理磁盘,一旦应用异常break down而重新启动,数据很难或者无法复原。
  • 硬盘:一般来说,很多缓存框架会结合使用内存和硬盘,在内存分配空间满了或是在异常的情况下,可以被动或主动的将内存空间数据持久化到硬盘中,达到释放空间或备份数据的目的。
  • 数据库:前面有提到,增加缓存的策略的目的之一就是为了减少数据库的I/O压力。现在使用数据库做缓存介质是不是又回到了老问题上了?其实,数据库也有很多种类型,像那些不支持SQL,只是简单的key-value存储结构的特殊数据库(如BerkeleyDB和Redis),响应速度和吞吐量都远远高于我们常用的关系型数据库等。

7.1.3 缓存分类和应用场景

缓存有各类特征,而且有不同介质的区别,那么实际工程中我们怎么去对缓存分类呢?在目前的应用服务框架中,比较常见的,时根据缓存雨应用的藕合度,分为local cache(本地缓存)和remote cache(分布式缓存):

  • 本地缓存:指的是在应用中的缓存组件,其最大的优点是应用和cache是在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适;同时,它的缺点也是应为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。

支付网关中的配置的map缓存

private static Map<String, Object> localCacheStoreMap = new HashMap<String, Object>();

把数据暂存在本地进程中,下次直接从map里面拿,减少对数据库的查找

适合于数据不常做变动的情况,比如,配置数据。这种情况下,需要做map的定时更新,或者结合ZooKeeper的统一管理,做到自动动态更新缓存

应用:

Ehcache

Ehcache是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大,是一个非常轻量级的缓存实现,我们常用的Hibernate里面就集成了相关缓存功能
在这里插入图片描述
从图中我们可以了解到,Ehcache的核心定义主要包括:

  • cache manager:缓存管理器,以前是只允许单例的,不过现在也可以多实例了。
  • cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口,这是一个真正使用的缓存实例;通过缓存管理器的模式,可以在单个应用中轻松隔离多个缓存实例,独立服务于不同业务场景需求,缓存数据物理隔离,同时需要时又可共享使用。
  • element:单条缓存数据的组成单位。
  • system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等,缓存就是从SOR中读取或者写入到SOR中去的。

Guava Cache

Guava Cache是Google开源的Java重用工具集库Guava里的一款缓存工具,其主要实现的缓存功能有:

  • 自动将entry节点加载进缓存结构中;
  • 当缓存的数据超过设置的最大值时,使用LRU算法移除;
  • 具备根据entry节点上次被访问或者写入时间计算它的过期机制;
  • 缓存的key被封装在WeakReference引用内;
  • 缓存的Value被封装在WeakReference或SoftReference引用内;
  • 统计缓存使用过程中命中率、异常率、未命中率等统计数据。

分布式缓存:指的是与应用分离的缓存组件或服务,其最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接的共享缓存。

  • redis 缓存应用
    • 在主页中显示最新的项目列表:Redis使用的是常驻内存的缓存,速度非常快。LPUSH用来插入一个内容ID,作为关键字存储在列表头部。LTRIM用来限制列表中的项目数最多为5000。如果用户需要的检索的数据量超越这个缓存容量,这时才需要把请求发送到数据库。
    • 删除和过滤:如果一篇文章被删除,可以使用LREM从缓存中彻底清除掉。
    • 排行榜及相关问题:排行榜(leader board)按照得分进行排序。ZADD命令可以直接实现这个功能,而ZREVRANGE命令可以用来按照得分来获取前100名的用户,ZRANK可以用来获取用户排名,非常直接而且操作容易。
    • 按照用户投票和时间排序:排行榜,得分会随着时间变化。LPUSH和LTRIM命令结合运用,把文章添加到一个列表中。一项后台任务用来获取列表,并重新计算列表的排序,ZADD命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索,即使是负载很重的站点。
    • 过期项目处理:使用Unix时间作为关键字,用来保持列表能够按时间排序。对current_time和time_to_live进行检索,完成查找过期项目的艰巨任务。另一项后台任务使用ZRANGE…WITHSCORES进行查询,删除过期的条目。
    • 计数:进行各种数据统计的用途是非常广泛的,比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易,通过原子递增保持计数;GETSET用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。
    • 特定时间内的特定项目:这是特定访问者的问题,可以通过给每次页面浏览使用SADD命令来解决。SADD不会将已经存在的成员添加到一个集合。
    • Pub/Sub:在更新中保持用户对数据的映射是系统中的一个普遍任务。Redis的pub/sub功能使用了SUBSCRIBE、UNSUBSCRIBE和PUBLISH命令,让这个变得更加容易。
    • 队列:在当前的编程中队列随处可见。除了push和pop类型的命令之外,Redis还有阻塞队列的命令,能够让一个程序在执行时被另一个程序添加到队列。

目前各种类型的缓存都活跃在成千上万的应用服务中,还没有一种缓存方案可以解决一切的业务场景或数据类型,我们需要根据自身的特殊场景和背景,选择最适合的缓存方案。缓存的使用是程序员、架构师的必备技能,好的程序员能根据数据类型、业务场景来准确判断使用何种类型的缓存,如何使用这种缓存,以最小的成本最快的效率达到最优的目的。

https://tech.meituan.com/2017/03/17/cache-about.html

7.2 中间件

7.2.1 消息中间件

使用消息中间件的优势:异步,解耦,削峰

遇到的问题:消息顺序(生产者消费者1对1),消息重复(业务端通过单号或者messageId保持幂等性)

概述

消息队列已经逐渐成为企业IT系统内部通信的核心手段。它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步RPC的主要手段之一。当今市面上有很多主流的消息中间件,如老牌的ActiveMQ、RabbitMQ,炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。

消息中间件的组成

  • 2.1 Broker
    • 消息服务器,作为server提供消息核心服务
  • 2.2 Producer
    • 消息生产者,业务的发起方,负责生产消息传输给broker,
  • 2.3 Consumer
    • 消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理
  • 2.4 Topic
    • 主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的 广播
  • 2.5 Queue
    • 队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收
  • 2.6 Message
    • 消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输

消息中间件模式分类

3.1 点对点

PTP点对点:使用queue作为通信载体
在这里插入图片描述
说明:

消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。

消息被消费以后,queue中不再存储,所以消息消费者不可能消费到已经被消费的消息。 Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。

3.2 发布/订阅

Pub/Sub发布订阅(广播):使用topic作为通信载体
在这里插入图片描述
说明:

消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。

queue实现了负载均衡,将producer生产的消息发送到消息队列中,由多个消费者消费。但一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有一个可用的消费者。

topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到一个消息的拷贝。

消息中间件的优势

系统解耦、加快系统响应时间、为大数据处理架构提供服务

异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。

应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。

流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。

日志处理 - 解决大量日志传输。

消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

最重要的三点:异步,解耦,削峰

消息中间件应用场景

异步通信、解耦、顺序保证 、缓冲

冗余

有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。

过载保护

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。

可恢复性

系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

数据流处理

分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。

7.2.1.1 RabbitMQ

使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP,STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。同时实现了Broker架构,核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)、数据持久化都有很好的支持。多用于进行企业级的ESB整合。

7.2.1.2 RocketMQ

阿里系下开源的一款分布式、队列模型的消息中间件,原名Metaq,3.0版本名称改为RocketMQ,是阿里参照kafka设计思想使用java实现的一套mq。同时将阿里系内部多款mq产品(Notify、metaq)进行整合,只维护核心功能,去除了所有其他运行时依赖,保证核心功能最简化,在此基础上配合阿里上述其他开源产品实现不同场景下mq的架构,目前主要多用于订单交易系统。

具有以下特点:

  • 能够保证严格的消息顺序
  • 提供针对消息的过滤功能
  • 提供丰富的消息拉取模式
  • 高效的订阅者水平扩展能力
  • 实时的消息订阅机制
  • 亿级消息堆积能力
7.2.1.3 ActiveMQ
7.2.1.4 Kafka

Apache下的一个子项目,使用scala实现的一个高性能分布式Publish/Subscribe消息队列系统,具有以下特性:

  • 快速持久化:通过磁盘顺序读写与零拷贝机制,可以在O(1)的系统开销下进行消息持久化;
  • 高吞吐:在一台普通的服务器上既可以达到10W/s的吞吐速率;
  • 高堆积:支持topic下消费者较长时间离线,消息堆积量大;
  • 完全的分布式系统:Broker、Producer、Consumer都原生自动支持分布式,依赖zookeeper自动实现复杂均衡;
  • 支持Hadoop数据并行加载:对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。
    在这里插入图片描述

7.2.2 远程过程调用(RPC)

7.2.2.1 Dubbo概述

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架(告别Web Service模式中的WSdl,以服务者与消费者的方式在dubbo上注册) 其核心部分包含:

  1. 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
  2. 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
  3. 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
7.2.2.2 Dubbo能做什么?
  • 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
  • 软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
  • 服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。

Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。

7.2.2.3 Dubbo的架构

在这里插入图片描述

节点说明:

  • Provider: 暴露服务的服务提供方。
  • Consumer: 调用远程服务的服务消费方
  • Registry: 服务注册与发现的注册中心。
  • Monitor: 统计服务的调用次调和调用时间的监控中心。
  • Container: 服务运行容器。

过程描述:

0 服务容器负责启动,加载,运行服务提供者。

  1. 服务提供者在启动时,向注册中心注册自己提供的服务。
  2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
  3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
  4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
  5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

7.2.3 docker

https://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html

7.2.3 k8s

https://zhuanlan.zhihu.com/p/103124918

7.2.3 es

基本介绍
es api接口的使用

8. spring、springboot、mybatis、Spring Cloud

面经:
1、设计模式
http://c.biancheng.net/view/1354.html

8.1 spring

面经:
1、aop
全局异常处理(ControllerAdvice)
https://www.cnblogs.com/xuwujing/p/10933082.html
2、di
3、bean生命周期

8.2 springboot

8.2.1 application.yml与bootstrap.yml的区别

面经:

1、application.properties和application.yml的加载顺序

答:properties的优先级高于yml

2、spring boot的自动装配?

https://www.cnblogs.com/hhcode520/p/9450933.html

3、springboot的拦截器怎么使用?

拦截器,过滤器,监听器,都属于web模块的内容,引入web-starter即可

https://www.cnblogs.com/hhhshct/p/8808115.html

Spring Boot 默认支持 properties(.properties) 和 YAML(.yml .yaml ) 两种格式的配置文件,yml 和 properties 文件都属于配置文件,功能一样。

Spring Cloud 构建于 Spring Boot 之上,在 Spring Boot 中有两种上下文,一种是 bootstrap,另外一种是 application,下面列举这两种配置文件的区别

  • 加载顺序
    若application.yml 和bootstrap.yml 在同一目录下:bootstrap.yml 先加载 application.yml后加载
    bootstrap.yml 用于应用程序上下文的引导阶段。bootstrap.yml 由父Spring ApplicationContext加载。
  • 配置区别
    bootstrap.yml 和 application.yml 都可以用来配置参数。
    bootstrap.yml 用来程序引导时执行,应用于更加早期配置信息读取。可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。一旦bootStrap.yml 被加载,则内容不会被覆盖。
    application.yml 可以用来定义应用级别的, 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等。
  • 属性覆盖问题
    启动上下文时,Spring Cloud 会创建一个 Bootstrap Context,作为 Spring 应用的 Application Context 的父上下文。
    初始化的时候,Bootstrap Context 负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的 Environment。Bootstrap 属性有高优先级,默认情况下,它们不会被本地配置覆盖。
    也就是说如果加载的 application.yml 的内容标签与 bootstrap 的标签一致,application 也不会覆盖 bootstrap,而 application.yml 里面的内容可以动态替换。
  • bootstrap.yml典型的应用场景
    当使用 Spring Cloud Config Server 配置中心时,这时需要在 bootstrap.yml 配置文件中指定 spring.application.name 和 spring.cloud.config.server.git.uri,添加连接到配置中心的配置属性来加载外部配置中心的配置信息
    一些固定的不能被覆盖的属性
    一些加密/解密的场景

8.2. 约定优于配置

在JDK5.0中引入注解的概念,就代表简化配置的开始,就是初期的一种约定优于配置的体现

约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。

补充:约定优于配置也被称为习惯优于配置、约定大于配置

本质来说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置。比如说模型中有一个名为User的类,那么数据库中对应的表就会默认命名为user。只有在偏离这一个约定的时候,例如想要将该表命名为system_user,才需要写有关这个名字的配置。

简单来说,如果你所用工具的约定和你的期待相符,就可以省去配置;不符合的话,你就要通过相关的配置来达到你所期待的结果。

约定优于配置意味着通用化,标准化,意味着开发者都需要遵循同一套约定。这样,当一个开发者要看另一个开发者写的程序的时候,就会很容易上手,因为他了解同一套约定,减少了重新学习的成本。

说白一点,就是默认配置要优于后面添加的配置

比如:

Maven项目的约定

源码目录为 src/main/java/

测试目录为 src/test/java/

打包方式为 jar

包输出目录为target/

Spring Boot项目的约定

Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。Spring Boot采用约定优于配置的方式,大量的减少了配置文件的使用。

如Spring Boot 中Tomcat默认的hostname是localhost,默认的端口是8080。

8.2.2 Spring Boot中怎么实现全局异常

通过 controllerAdvice注解ExceptionHandler注解,以及自定义的异常类和枚举类来实现我们想要的全局异常
https://www.cnblogs.com/xuwujing/p/10933082.html

8.3mybatis

面经:

#{}和${}的区别?

  • #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。

  • Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。

  • Mybatis在处理时 , #{}是原值传入,就是把{}替换成变量的值,相当于JDBC中的Statement编译

  • 变量替换后,#{} 对应的变量自动加上单引号 ‘;变量替换后,${} 对应的变量不会加上单引号 ‘

  • #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入

  • #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外

MyBatis实现一对一,一对多有几种方式,怎么操作的?

有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的association,collection节点配置一对一,一对多的类就可以完成

嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过配置association,collection,但另外一个表的查询通过select节点配置。

mybatis面经:https://thinkwon.blog.csdn.net/article/details/101292950

8.3.1 MyBatis工作原理

在这里插入图片描述

8.3.2 MyBatis功能架构

在这里插入图片描述

API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。

数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

8.3.3 缓存

一级缓存是SqlSession级别的缓存,每个SqlSession对象都有一个哈希表用于缓存数据,不同SqlSession对象之间缓存不共享。同一个SqlSession对象对象执行2遍相同的SQL查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库查询了,直接返回缓存结果即可。MyBatis默认是开启一级缓存的。

二级缓存是mapper级别的缓存,二级缓存是跨SqlSession的,多个SqlSession对象可以共享同一个二级缓存。不同的SqlSession对象执行两次相同的SQL语句,第一次会将查询结果进行缓存,第二次查询直接返回二级缓存中的结果即可。MyBatis默认是不开启二级缓存的,可以在配置文件中使用如下配置来开启二级缓存:

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

当SQL语句进行更新操作(删除/添加/更新)时,会清空对应的缓存,保证缓存中存储的都是最新的数据。

MyBatis的二级缓存对细粒度的数据级别的缓存实现不友好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用MyBatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为MyBatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存,具体业务具体实现。

8.4 Spring Cloud

这篇文章中初步讲解了 Spring Cloud 的各个组件,他们有

  • Eureka 服务发现框架
  • Ribbon 进程内负载均衡器
  • Open Feign 服务调用映射
  • Hystrix 服务降级熔断器
  • Zuul 微服务网关
  • Config 微服务统一配置中心
  • Bus 消息总线

在这里插入图片描述
https://zhuanlan.zhihu.com/p/95696180?from_voters_page=true

9. Linux

9.1 Linux注意事项

  • Linux的命令严格区分大小写(Linux中的所有字母都是区分大小写的)
  • Linux的所有东西都是文件;包括硬件
    • 硬盘文件:/dev/sd[a-p]
    • 光盘文件:/dev/sr0等
  • Linux文件都是“没有”后缀名的,是靠 文件权限来区分的
    • 但是有一些压缩包文件(tgz,tar,bz2,gz…),脚本文件(sh),配置文件(conf),网页文件(html,css,php,java),这些所谓的扩展名,都仅仅是为了方便管理员来管理文件,没有扩展名照样使用。
  • Linux的所有存储设备都得挂载(分配盘符)以后用户才能使用,比如,硬盘,光盘,U盘
  • windows下的程序不能直接在Linux中安装运行的

9.2 如何选择 Linux 操作系统版本?

一般来讲,桌面用户首选 Ubuntu ;服务器首选 RHEL 或 CentOS ,两者中首选 CentOS 。

根据具体要求:

安全性要求较高,则选择 Debian 或者 FreeBSD 。

需要使用数据库高级服务和电子邮件网络应用的用户可以选择 SUSE 。

想要新技术新功能可以选择 Feddora ,Feddora 是 RHEL 和 CentOS 的一个测试版和预发布版本。

【重点】根据现有状况,绝大多数互联网公司选择 CentOS 。现在比较常用的是 6 系列,现在市场占有大概一半左右。另外的原因是 CentOS 更侧重服务器领域,并且无版权约束。

CentOS 7 系列,也慢慢使用的会比较多了。

9.3 如何规划一台 Linux 主机,步骤是怎样?

1、确定机器是做什么用的,比如是做 WEB 、DB、还是游戏服务器。

不同的用途,机器的配置会有所不同。

2、确定好之后,就要定系统需要怎么安装,默认安装哪些系统、分区怎么做。

3、需要优化系统的哪些参数,需要创建哪些用户等等的。

9.4 当用户反馈网站访问慢,如何处理?

有哪些方面的因素会导致网站网站访问慢?

1、服务器出口带宽不够用

本身服务器购买的出口带宽比较小。一旦并发量大的话,就会造成分给每个用户的出口带宽就小,访问速度自然就会慢。

跨运营商网络导致带宽缩减。例如,公司网站放在电信的网络上,那么客户这边对接是长城宽带或联通,这也可能导致带宽的缩减。

2、服务器负载过大,导致响应不过来

可以从两个方面入手分析:

分析系统负载,使用 w 命令或者 uptime 命令查看系统负载。如果负载很高,则使用 top 命令查看 CPU ,MEM 等占用情况,要么是 CPU 繁忙,要么是内存不够。

如果这二者都正常,再去使用 sar 命令分析网卡流量,分析是不是遭到了攻击。一旦分析出问题的原因,采取对应的措施解决,如决定要不要杀死一些进程,或者禁止一些访问等。

3、数据库瓶颈

如果慢查询比较多。那么就要开发人员或 DBA 协助进行 SQL 语句的优化。

如果数据库响应慢,考虑可以加一个数据库缓存,如 Redis 等。然后,也可以搭建 MySQL 主从,一台 MySQL 服务器负责写,其他几台从数据库负责读。

4、网站开发代码没有优化好

例如 SQL 语句没有优化,导致数据库读写相当耗时。

9.5 针对网站访问慢,怎么去排查?

1、首先要确定是用户端还是服务端的问题。当接到用户反馈访问慢,那边自己立即访问网站看看,如果自己这边访问快,基本断定是用户端问题,就需要耐心跟客户解释,协助客户解决问题。

不要上来就看服务端的问题。一定要从源头开始,逐步逐步往下。

2、如果访问也慢,那么可以利用浏览器的调试功能,看看加载那一项数据消耗时间过多,是图片加载慢,还是某些数据加载慢。

3、针对服务器负载情况。查看服务器硬件(网络、CPU、内存)的消耗情况。如果是购买的云主机,比如阿里云,可以登录阿里云平台提供各方面的监控,比如 CPU、内存、带宽的使用情况。

4、如果发现硬件资源消耗都不高,那么就需要通过查日志,比如看看 MySQL慢查询的日志,看看是不是某条 SQL 语句查询慢,导致网站访问慢。

怎么去解决?

1、如果是出口带宽问题,那么久申请加大出口带宽。

2、如果慢查询比较多,那么就要开发人员或 DBA 协助进行 SQL 语句的优化。

3、如果数据库响应慢,考虑可以加一个数据库缓存,如 Redis 等等。然后也可以搭建MySQL 主从,一台 MySQL 服务器负责写,其他几台从数据库负责读。

4、申请购买 CDN 服务,加载用户的访问。

5、如果访问还比较慢,那就需要从整体架构上进行优化咯。做到专角色专用,多台服务器提供同一个服务。

9.6 Linux 性能调优都有哪几种方法?

1、Disabling daemons (关闭 daemons)。

2、Shutting down the GUI (关闭 GUI)。

3、Changing kernel parameters (改变内核参数)。

4、Kernel parameters (内核参数)。

5、Tuning the processor subsystem (处理器子系统调优)。

6、Tuning the memory subsystem (内存子系统调优)。

7、Tuning the file system (文件系统子系统调优)。

8、Tuning the network subsystem(网络子系统调优)。

9.7 各个目录的功能(大部分都是一种规范约定)

目录名目录作用
/bin存放系统命令的目录,普通用户和超级用户都可以执行,不过放在/bin下的命令在单用户模式下也可以执行
/sibn保存和系统环境设置相关的命令,只有超级用户可以使用这些命令镜像系统环境的设置,但有些命令可以允许普通用户查看
/uer/bin/存放系统命令的目录,普通用户和超级用户都可以执行,这些命令和系统启动无关,在单用户模式下不能执行
/usr/sbin/存放根文件系统不必要的系统管理命令,例如多数服务程序,只有超级用户可以使用,其实,在Linux中,在sbin目录中的命令只有超级用户能用,bin下的所有用户都可以用
/boot/系统启动目录,保存系统启动相关的文件,如内核文件和启动引导程序文件(grub)等
/dev/设备文件保存位置,一些硬件文件,硬盘等等
/etc/配置文件保存位置。系统内所有采用默认安装方式(rpm安装)的服务的配置文件全部在这个目录保存,如:用户账号密码,服务启动脚本,一些服务的配置文件
/home/普通用户的家目录,每个用户都要有一个默认登录位置,这个位置就是该用户的家目录,所有普通用户的家目录就是在/home下建立一个和用户名相同的目录,如:用户user的家目录:/home/user
/lib/系统调用的函数库保存位置
/lost+found/当系统意外崩溃或者机器意外关机,而产生的一些文件碎片放在这里,当系统启动的过程中fsck工具会检查这里,并修复已经损坏的文件系统,这个目录只在每个分区中出现,例如 /lost+found/就是根分区的备份恢复目录,/boot/lost+found/ 就是/boot分区的备份恢复目录
/media/挂载目录,系统建议是用来挂载媒体设备的,例如软盘和光盘
/mnt/挂载目录,早起Linux中只有这一个挂载目录,并没有细分,现在这个目录系统建议挂载额外设备,例如,u盘,移动硬盘和其他系统的分区
/misc/挂载目录,系统建议用来挂载NFS服务的共享目录
/opt/第三方安装的软件保存位置,这个目录就是防止和安装其他软件的位置,比如 jdk,tomcat…,但也不强迫,有的人喜欢放到usr下也是可以的
proc虚拟文件系统,该目录中的数据并不保存到硬盘中,而是保存在内存中,主要保存系统的内核,进程,外部设备状态和网络装填灯
/sys/虚拟文件系统,和/proc目录相似,保存在内存中,保存内核相关信息
/root/超级用户的家目录,即 /
/srv/服务数据目录,一些系统服务启动之后,可以在这个目录中保存所需要的的数据
/tmp/临时目录,系统存放临时文件的目录,该目录下所有用户都可以访问和写入,我们建议此目录中不能重要数据,每次开机最好清空此目录
/usr/系统软件资源目录,注意usr不是user的缩写,而是“Unix Software Resource”的缩写,所以不是存放用户数据,而是存放系统软件资源的目录,系统中安装的软件大多保存这里
/var/动态数据保存位置,主要保存缓存和日志以及软件运行所产生的的文件

proc和sys是存放在内存中的两个目录,重启会消失。不要往里面写东西,东西一来不会保存,而且当你写满以后就会系统就会出错

usr,系统软件资源目录(system,software,resource)类似于windows的windows目录

注意事项:

1、远程服务器不允许关机,只能重启

2、重启服务应该关闭运行着的服务!!

3、不要在服务器访问高峰运行高负载命令:高负载命令:进行大数据的操作,压缩,扫描复制等等

4、远程配置防火墙的时候记得对自己开放,防火墙甄别是通过:ip地址,端口号,net地址,包数据

5、数据和日志的定时备份,权限的合理分配(最小分配,够用就行),密码的强壮性和定时更新,系统启动的服务越少越好

9.8 常用命令

9.8.1 |

Linux所提供的管道符“|”将两个命令隔开,管道符左边命令的输出就会作为管道符右边命令的输入。连续使用管道意味着第一个命令的输出会作为 第二个命令的输入,第二个命令的输出又会作为第三个命令的输入,依此类推。下面来看看管道版是如何在构造一条Linux命令中得到应用的。

例如:

在文件中检索含有对应字符串的内容(日志中查询需要的内容)

cat fileName | grep '查询的内容'

cat -n fileName | grep -rn '21-05-19 15:3[0-9]'

ps -ef | grep xxxx.jar,将(ps命令)所得到进程信息,进行二次筛选(内容中含有xxxx.jar的信息)

在这里插入图片描述

9.8.2 ls / ll

ll是一个查看当前目录文件列表的命令

就是list的缩写,通过ls 命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)查看目录信息等等

 (1) 按易读方式按时间反序排序,并显示文件详细信息
 
 ls -lhrt

 (2) 按大小反序显示文件详细信息

 ls -lrS

 (3)列出当前目录中所有以“t”开头的目录的详细内容

 ls -l t*

 (4) 列出文件绝对路径(不包含隐藏文件)

 ls | sed "s:^:`pwd`/:"

 (5) 列出文件绝对路径(包含隐藏文件)

 find $pwd -maxdepth 1 | xargs ls -ld

9.8.3 cd

(1)进入根目录
	cd /
(2)进入当前用户的目录
	cd ~
(3)进入上一次工作路径
	cd -
(4)把上个命令的参数作为cd参数使用。
	cd !$

9.8.4 pwd

查看当前工作目录路径

9.8.5 rm

rm [选项] 文件…

(1)删除任何.log文件;删除前逐一询问确认
	rm -i *.log
(2)删除test子目录及子目录中所有档案删除,并且不用一一确认
	rm -rf test
(3)删除以-f开头的文件
	rm -- -f*

9.8.6 mv

移动文件或修改文件名,根据第二参数类型(如目录,则移动文件;如为文件则重命令该文件)。

当第二个参数为目录时,可刚多个文件以空格分隔作为第一参数,移动多个文件到参数2指定的目录中

(1)将文件test.log重命名为test1.txt
	mv test.log test1.txt
(2)将文件log1.txt,log2.txt,log3.txt移动到根的test3目录中
	mv llog1.txt log2.txt log3.txt /test3
(3)将文件file1改名为file2,如果file2已经存在,则询问是否覆盖
	mv -i log1.txt log2.txt
(4)移动当前文件夹下的所有文件到上一级目录
	mv * ../

9.8.7 cp

将源文件复制至目标文件,或将多个源文件复制至目标目录。

注意:命令行复制,如果目标文件已经存在会提示是否覆盖,而在shell脚本中,如果不加-i参数,则不会提示,而是直接覆盖!

-i 提示

-r 复制目录及目录内所有项目

-a 复制的文件与原文件时间一样

实例:

(1)复制a.txt到test目录下,保持原文件时间,如果原文件存在提示是否覆盖
	cp -ai a.txt test
(2)为a.txt建议一个链接(快捷方式)
	cp -s a.txt link_a.txt

9.8.8 less

less与 more 类似,但使用 less 可以随意浏览文件,而 more 仅能向前移动,却不能向后移动,而且 less 在查看之前不会加载整个文件。

 常用命令参数

 -i  忽略搜索时的大小写

 -N  显示每行的行号

 -o  <文件名> 将less 输出的内容在指定文件中保存起来

 -s  显示连续空行为一行

 /字符串:向下搜索“字符串”的功能

 ?字符串:向上搜索“字符串”的功能

 n:重复前一个搜索(与 / 或 ? 有关)

 N:反向重复前一个搜索(与 / 或 ? 有关)

 -x <数字> 将“tab”键显示为规定的数字空格

 b  向后翻一页

 d  向后翻半页

 h  显示帮助界面

 Q  退出less 命令

 u  向前滚动半页

 y  向前滚动一行

 空格键 滚动一行

 回车键 滚动一页

 [pagedown]: 向下翻动一页

 [pageup]:   向上翻动一页

 实例:
 (1)ps查看进程信息并通过less分页显示
 	ps -aux | less -N
 (2)查看多个文件
 	less 1.log 2.log
 可以使用n查看下一个,使用p查看前一个

9.8.9 find

find 在根目录下找 按照名字来找 找名字为abc的文件
find / -name abc
find 在根目录下找 按照名字来找 找名字为abc的文件,左右?匹配任意一个字符
find / -name ?abc?
find / -name abc?
find / -name ?abc
find 在根目录下找 按照名字来找 找名字为abc的文件,左右*匹配任意字符
find / -name *abc*
find / -name *abc
find / -name abc*

find 在根目录下找 按照名字(不区分大小写)来找 找名字以abc结尾的文件
find / -iname *abc

9.8.10 ps

ps(process status),用来查看当前运行的进程状态,一次性查看,如果需要动态连续结果使用top

 linux上进程有5种状态:

 1. 运行(正在运行或在运行队列中等待)

 2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)

 3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)

 4. 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait4()系统调用后释放)

 5. 停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)

 ps工具标识进程的5种状态码:

 D 不可中断 uninterruptible sleep (usually IO)

 R 运行 runnable (on run queue)

 S 中断 sleeping

 T 停止 traced or stopped

 Z 僵死 a defunct (”zombie”) process

 命令参数:

 -A 显示所有进程

 a 显示所有进程

 -a 显示同一终端下所有进程

 c 显示进程真实名称

 e 显示环境变量

 f 显示进程间的关系

 r 显示当前终端运行的进程

 -aux 显示所有包含其它使用的进程

 实例:

(1)显示当前所有进程环境变量及进程间关系
	ps -ef
(2)显示当前所有进程
	ps -A
(3)与grep联用查找某进程
	ps -aux | grep apache
(4)找出与 cron 与 syslog 这两个服务有关的 PID 号码
	ps aux | grep '(cron|syslog)'

9.8.11 kill

发送指定的信号到相应进程。不指定型号将发送SIGTERM(15)终止指定进程。如果任无法终止该程序可用“-KILL” 参数,其发送的信号为SIGKILL(9) ,将强制结束进程,使用ps命令或者jobs 命令可以查看进程号。root用户将影响用户的进程,非root用户只能影响自己的进程。

 常用参数:

 -l  信号,若果不加信号的编号参数,则使用“-l”参数会列出全部的信号名称

 -a  当处理当前进程时,不限制命令名和进程号的对应关系

 -p  指定kill 命令只打印相关进程的进程号,而不发送任何信号

 -s  指定发送信号

 -u  指定用户

 实例:

 (1)先使用ps查找进程pro1,然后用kill杀掉

 kill -9 $(ps -ef | grep pro1)

9.8.12 scp

输入:scp /home/helpteach/project/mallupload/1509681299449.png wasadmin@10.127.40.25:/home/test

然后拍回车键就可以看到文件正在传输了,等再出现鼠标一闪一闪说明已经传输完毕了,给大家拆解一下语句

对拷文件夹 (包括文件夹本身)

scp -r /home/helpteach/project/mallupload/ wasadmin@10.127.40.25:/home/test

对拷文件夹下所有文件 (不包括文件夹本身)

scp /home/helpteach/project/mallupload/* wasadmin@10.127.40.25:/home/test

对拷文件并重命名

scp /home/helpteach/project/mallupload/1509681299449.png wasadmin@10.127.40.25:/home/test/test.png

/home/helpteach/project/mallupload/1509681299449.png:要传输的文件

wasadmin:目标服务器ssh账号名

@后面:目标服务器的IP地址

:后面:要传输到目标服务器的文件保存目录

9.8.13 free / top / w

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

10. JDK 1.8的新特性

10.1 Lambda表达式

10.2 Optional

10.3 时间日期工具类

10.4 Stream流

https://blog.csdn.net/thinkwon/category_10805022.html

10.1 Lamda表达式的来源

首先引入default关键字的使用

jdk1.8之前在下面两个地方使用到:

  • switch语句中的默认分支
  • 注解中的字段默认值

1.8之后可以 定义接口的默认方法

比如collection中的排序方法:

在这里插入图片描述
直接调用,在接口中有了实现。

默认方法是什么:

  • jdk1.8中包括default关键字方法定义的方法为默认方法

默认方法的多次重写:
默认方法重写的策略是什么?

  • 可以被重写为抽象方法后再被重写回默认方法,或者在抽象类中重写实现。
    在这里插入图片描述
    例子:学生类的排序
@Data
@AllArgsConstructor
private class Student {
	private String name;
	private int age;
	private float score;
	private Gender gender;
}

11. java基础

序号内容链接地址
1Java并发关键字-synchronizedhttps://thinkwon.blog.csdn.net/article/details/102243189
2Java并发关键字-volatilehttps://thinkwon.blog.csdn.net/article/details/102243670
3Java并发关键字-finalhttps://thinkwon.blog.csdn.net/article/details/102244477

synchronized加类上和方法上和对象上有什么不一样:https://www.cnblogs.com/codebj/p/10994748.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值