面向面试编程

2023年2月2日09:58:57更新

提问:请问什么是java中函数式接口?
回答:只定义了一个抽象方法的接口。(如果你也是这个回答,很遗憾肯定是不及格的)

原答案中对于此回答不是很认可,我不知道是处于语法的严谨还是个人角度理解的不同,在官方文档中如下

1
2
3
Package java.util.function Description
Functional interfaces provide target types for lambda expressions and method references. Each functional interface has a single abstract method, called the functional method for that functional interface, to which the lambda expression's parameter and return types are matched or adapted. Functional interfaces can provide a target type in multiple contexts, such as assignment context, method invocation, or cast context:

译文如下:
函数接口为 lambda 表达式和方法引用提供目标类型。每个函数接口都有一个抽象方法,称为该函数接口的函数方法,lambda 表达式的参数和返回类型与该方法匹配或调整。函数接口可以在多个上下文中提供目标类型,例如赋值上下文、方法调用或强制转换上下文

jdk哪个方法是用堆实现的

PriorityQueue

函数式编程的本质是什么?

函数式编程的本质是:把函数看作是数据。

Stream.foreach中类型是什么?

1
2
3
4
5
6
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}

1.请介绍下java中基本数据类型以及它们的使用场景

Java八大数据类型:
(1)整数类型:byte、short、int、long
(2)小数类型:float、double
(3)字符类型:char
(4)布尔类型:boolean
引用数据类型
String

2.为什么定义了这些基本数据类型后还要定义包装类?

之所以需要包装类型,就是因为java是一个面向对象的语言,然而基本数据类型不具备面向对象的特性,当我们把基本数据类型包装成包装类型之后,它就具有了面向对象的特性。而且,在往ArrayList、HashMap这些容器传数据的时候,基本类型int和double是传输不进去的,因为容器都是装object类型的,所以需要转为包装类型进行传输。每一个基本数据类型都有对应的包装类型.

3.包装类和String类有什么相同点吗?

都允许为null或空,
包装类除Float,Double并没有实现常量池技术,其他的和String类都存放在常量池中。

4.包装类是否重写了equals方法,为什么?

是的,先看他是否内存相等,如果不相等

5.请问我使用Integer定义两个数字,它们值都等于100,使用 == 和equals方式分别比较它们是否相等?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
都是true

Integer a =100;
Integer b =100;


System.out.println(a == b);
System.out.println(a.equals(b));

输出
true
true
-------
Integer a =200;
Integer b =200;


System.out.println(a == b);
System.out.println(a.equals(b));

输出
false
true

6.导致上面结果原因是什么?如果我把值都改成200呢,结果会发生什么改变?

在内存中的缓存值是相等的。优先比较内存,200超过127的大小范围==是不相等的

7.我如何验证上述结果原因?

Integer的缓存机制:为了节省内存和提高性能,Integer类在内部通过使用相同的对象引用实现缓存和重用,Integer类默认在-128 ~ 127 之间,可以通过 -XX:AutoBoxCacheMax进行修改,且这种机制仅在自动装箱的时候有用,在使用构造器创建Integer对象时无用。

8.哪些包装类是带缓存的?默认值是多少?

Integer 、Byte 、Short 、Long 、Character 五大包装类都有缓冲机制,且缓冲的默认值范围都是-128~127

而Float,Double,Boolean 三大包装类并没有缓冲机制。

9.我是否可以改变缓存值区间?怎么做?

可以通过 -XX:AutoBoxCacheMax进行修改,且这种机制仅在自动装箱的时候有用,在使用构造器创建Integer对象时无用。

1.请简单说下mysql常用索引类型

主键索引、唯一索引、普通索引、全文索引、组合索引(联合索引,多列索引)

2.组合索引使用时有什么需要特别注意的?

1、对于复合索引,在查询使用时,最好将条件顺序按找索引的顺序,这样效率最高; select * from table1 where col1=A AND col2=B AND col3=D 如果使用 where col2=B AND col1=A 或者 where col2=B 将不会使用索引
2、何时是用复合索引 根据where条件建索引是极其重要的一个原则; 注意不要过多用索引,否则对表更新的效率有很大的影响,因为在操作表的时候要化大量时间花在创建索引中
3、复合索引会替代单一索引么 如果索引满足窄索引的情况下可以建立复合索引,这样可以节约空间和时间

3.为哪个表哪个字段需要添加索引有什么依据吗?

1、表的主键、外键必须有索引;
2、数据量超过300的表应该有索引;
3、经常与其他表进行连接的表,在连接字段上应该建立索引;
4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
5、索引应该建在选择性高的字段上;
6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
7、复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替:
A、正确选择复合索引中的主列字段,一般是选择性较好的字段;
B、复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;
C、如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
D、如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段;
E、如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
8、频繁进行数据操作的表,不要建立太多的索引;
9、删除无用的索引,避免对执行计划造成负面影响;
以上是一些普遍的建立索引时的判断依据。一言以蔽之,索引的建立必须慎重,对每个索引的必要性都应该经过仔细分析,要有建立的依据。因为太多的索引与不充分、不正确的索引对性能都毫无益处:在表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大

4.能为较长的varchar类型字段建立索引吗?建立哪种索引?

其中M指的是可存储的字符长度(或字符数),而MySQL实际是按字节存储的,在不同的字符集下一个字符的字节长不同,因此这个M最大值在不同的字符集下值不同:

对于latin字符集下,因为一个字符占一个字节,所以M的最大值为65535(但实际只有65532);对于gbk字符集,因为一个字符占两个字节,所以M的最大值为32767;对于utf8字符集,因为一个字符占两到三个字节,所以M的最大值为21845。

此外,mysql官方文档中定义的65535长度是指同一行的所有varchar列的长度总和。如果列的长度总和超出这个长度,依然无法创建。

1、MySQL5.6的限制方式:

在MySQL5.6版本中,当某个列的varchar长度定义超过相应字符集下的最大长度时,会自动将该列转存为mediumtext类型。例如,在utf8字符集下,定义ecs_payment表test2字段长度为21846:

假如再存储一个字段test3,定义varchar长度为21845,这时没有超过最大长度限制,但在存储test3 varchar(21845)列时,发现该表上所有varchar行的总长度将会超过65535字节,因此会发生如下报错:

1
2
mysql> alter table ecs_payment add test3 varchar(21845);
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

2、MySQL5.7的限制方式:

在MySQL5.7版本下,只要列的varchar长度超过相应字符集下的最大限制,或者表上所有varchar列总长度将会超过65535字节时,MySQL都会抛出错误提示:

1
2
3
4
mysql> alter table t1 add c1 varchar(21846);
ERROR 1074 (42000): Column length too big for column 'c1' (max = 21845); use BLOB or TEXT instead
mysql> alter table t1 add c1 varchar(21844);
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

二、创建索引的限制
对于varchar列,当varchar长度过长时,会对索引的创建有限制,在MySQL5.6和5.7下的限制行为的表现形式不同。

1、MySQL5.6的限制

在MySQL5.6中,对ecs_payment表的test varchar(1024)列创建索引,并查看创建后的情况:

可以看到test列上建立了一个前缀索引,前缀长度为255字节。在MySQL5.6下,varchar长度超过255字节时是不适合建立索引的,MySQL会自动只建立255字节长的前缀索引,而不是抛出错误。

2、MySQL5.7的限制

在MySQL5.7版本下,varchar列上可建索引的最大长度是3072字节,超过此长度在建索引时会报错:

1
2
3
4
5
mysql> alter table t1 add column c4 varchar(1025);
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> create index i_2 on t1(c4);
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes

表t1是utf8字符集。

5.在你之前开发经验中,你还有哪些索引使用规范?

1、 只为用于搜索、排序或分组的列创建索引。
重点关注 where 语句后边的情况

2、 当列中不重复值的个数在总记录条数中的占比很大时,才为列建立索引。
例如手机号、用户 ID、班级等,但是比如一张全校学生表,每条记录是一名学生,where 语句是查询所有’某学校‘的学生,那么其实也不会提高性能。

3、 索引列的类型尽量小。
无论是主键还是索引列都尽量选择小的,如果很大则会占据很大的索引空间。

4、 可以只为索引列前缀创建索引,减少索引占用的存储空间。
alter table single_table add index idx_key1(key1(10))
5、 尽量使用覆盖索引进行查询,以避免回表操作带来的性能损耗。
select key1 from single_table order by key1

6、 为了尽可能的少的让聚簇索引发生页面分裂的情况,建议让主键自增。
7、 定位并删除表中的冗余和重复索引。

冗余索引: 指的是不同的联合索引组合,某一列或者几列字段被多组索引覆盖,一般称这些列存在冗余索引

查询冗余索引SQL

1
2
3
4
5
6
7
8
9
SELECT a.TABLE_SCHEMA, a.TABLE_NAME, a.COLUMN_NAME, 
a.INDEX_NAME AS 'index1', b.INDEX_NAME AS 'index2'
FROM information_schema.STATISTICS a
JOIN information_schema.STATISTICS b
ON a.TABLE_SCHEMA = b.TABLE_SCHEMA
AND a.TABLE_NAME = b.TABLE_NAME
AND a.SEQ_IN_INDEX = b.SEQ_IN_INDEX
AND a.COLUMN_NAME = b.COLUMN_NAME
WHERE a.SEQ_IN_INDEX = 1 AND a.INDEX_NAME <> b.INDEX_NAME

单列索引:(字段 1)

联合索引:(字段 1 字段 2)

重复索引:在一个字段上添加了普通索引、唯一索引、主键等多个索引

6.一般我们是如何查看一条sql语句索引有没有起作用的?

explain执行分析计划
我们只需要注意一个最重要的type 的信息很明显的提现是否用到索引:

type结果值从好到坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般来说,得保证查询至少达到range级别,最好能达到ref,否则就可能会出现性能问题。

possible_keys:sql所用到的索引

key:显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL

rows: 显示MySQL认为它执行查询时必须检查的行数

3、profiling分析
想要优化一条query sql ,就要清楚这条query的性能瓶颈在哪里,mysql的profiler是一个非常方便的查询诊断分析工具,通过该工具可以获取一条查询在整个执行过程中多种资源的消耗情况,例如内存消耗、I/O消耗和CPU消耗
profile的语法结构:
show profile [type [,type] …]
[for query n]
[limit row_count [offset offset]]
其中type参数可选含义如下:
all:显示所有信息
block io:显示输入输出操作阻塞的数量
context switches:显示自动或非自动context switches的数量
cpu:显示系统和用户CPU使用的时间
ipc:显示信息发送和接受的数量
memory:内存的信息
page faults:显示主要的page faults数量
source:显示函数的名称,并且是那些函数所在文件的名字和行数
swaps:显示swap数量

开启profile
set profiling = 1;

开启query profiler功能之后,MySQL就会自动记录所有执行的query的profile信息

1
select count(*) from customers1;

通过执行show profiles 命令获取当前系统中保存的多个query的profile的概要信息

针对单个query获取详细的profile信息(根据概要信息中的query_id来获取)
show profile for query 5;

7.有没有了解过为什么添加索引可以加快查询速度?(数据结构B树和B+树)

首先明白为什么索引会增加速度,DB在执行一条Sql语句的时候,默认的方式是根据搜索条件进行全表扫描,遇到匹配条件的就加入搜索结果集合。如果我们对某一字段增加索引,查询时就会先去索引列表中一次定位到特定值的行数,大大减少遍历匹配的行数,所以能明显增加查询的速度。
MySQL官方对于索引的定义为:索引是帮助MySQL高效获取数据的数据结构。即可以理解为:索引是数据结构。

我们知道,数据库查询是数据库最主要的功能之一,我们都希望查询数据的速度尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。最基本的查询算法当然是顺序查找,当然这种时间复杂度为O(n)的算法在数据量很大时显然是糟糕的,于是有了二分查找、二叉树查找等。但是二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树,但是数据本身的组织结构不可能完全满足各种数据结构。所以,在数据之外,数据库系统还维护者满足特定查找算法的数据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
数据库索引采用B+树的主要原因是 B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题。正是为了解决这个问题,B+树应运而生。B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作(或者说效率太低)

8.二者有什么区别?(可以给一个例子让其画出来)

缺页查询,减少io

9.结合树的特点说说,为什么推荐使用自增ID来做索引?为什么不使用红黑树、hash树?

自增主键的插入数据模式,正符合了递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。
而有业务逻辑的字段做主键,则往往不容易保证有序插入,这样写数据成本相对较高。

@Service 和@Component 注解的差别?

@Component spring基础的注解,被spring管理的组件或bean,用于将对象实例化到Spring容器中
而@Service源码中是包含@Component注解的,也就是说service实现component的功能,但service用于服务层,处理业务逻辑

各种 Restful 请求格式以及各种 http 请求返回码。

1
2
3
API,英文全称Application Programming Interface,翻译为“应用程序编程接口”。就是将一些功能(逻辑)封装成组件,目的是提供一个应用程序接口给其它程序与开发人员访问,而这些访问人员不需要访问源码以及理解内部工作原理就可以直接使用
RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,使用JSON格式的REST风格的API具有简单、易读、易用的特点。
而http返回状态码常见200.400.500等

1.请说下Springboot相比较Spring来说,你认为的最重要的三个特点是什么?

自动化装配(以规约大于配置思想,做到了很多功能模块的自动化装配)、内嵌容器化(可以独立以jar包方式运行无需外部web容器支持)、开发运维化(基于一些devops思想做了一些endpoint来支持监控管理化)

2.请问springboot的自动化装配技术,哪些技术来源与spring体系,哪些是自己新增的?

SpringBoot中的一些特征:

1、创建独立的 Spring应用。

2、嵌入式 Tomcat、 Jetty、 Undertow容器(无需部署war文件)。

3、提供的 starters 简化构建配置

4、尽可能自动配置 spring应用。 5、提供生产指标,例如指标、健壮检查和外部化配置

6、完全没有代码生成和 XML配置要求

3-N:对上文的模式注解、模块装配、条件装配知识点进行具体有层次的提问

Spirng模式注解装配
@Component作为一种由Spirng容器托管的通用模式组件,任何被@Component标准的组件均为组件扫描的候选对象.类似的,凡是被@Component原标注的注解,如@Service,任何组件标注它时,也将被是做组件扫描的候选对象.

Spring @Enable模块装配

1
2
3
4
5
6
7
@EnableFeignClients(basePackages = {""})
@EnableDiscoveryClient
@EnableTransactionManagement
@SpringBootApplication(scanBasePackages = {""})
@EnableScheduling
@ServletComponentScan
@EnableAsync(proxyTargetClass = true)

Spirng条件装配

Spring Boot 提供的条件注解

1
2
3
4
5
6
7
8
9
10
11
12
@ConditionalOnBean:当容器里有指定 Bean 的条件下
@ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
@ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
@ConditionalOnClass:当类路径下有指定类的条件下
@ConditionalOnMissingClass:当类路径下没有指定类的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnExpression:基于 SpEL 表达式作为判断条件
@ConditionalOnJava:基于 Java 版本作为判断条件
@ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
@ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
@ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

拿模块装配为例子可以继续提问:自定义的模块装配有几种实现方式? 自动化注解方式和selector接口编程的方式这两种比较各有什么特点?我们如何选择? 可以各举个spring中实际实现的例子吗?

关于Java的Selector,其实也没什么好说的。说高级点就是就是多路复用。而多路复用是由于操作系统的支持,才能得以实现。适合实时性要求高的场景
而对于自动化注解则是常用的驱动方式,适合方面是编码以及优化方面的

@Transactional 事务里的事务隔离级别和事务传播机制概念。

定义一个事务受其他并发事务影响程度。事务并发引发的问题。

脏读:一个事务读取到另一个事务修改但还未提交的数据
不可重复读:一个事务读取数据之后,该数据被其他事务修改,此时第一个事务读取到的事务就是错误的(强调修改)
幻读:一个事务读取了某些数据,没提交再读取时数据多了或者少了,类似幻觉(强调增删)
丢失修改:两个事务都读取了数据,其中一个事务修改之后,另一个事务也做了修改,前者的修改丢失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//可以放在 类上 或者 方法上。 
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

/**
* 区别于 transactionManager属性
* 查看 transactionManager
*/
@AliasFor("transactionManager")
String value() default "";

/**
* 选定特定的 事务管理器 SpringBoot中默认不用配置 可以是beanName或者特定的值
*/
@AliasFor("value")
String transactionManager() default "";

/**
* 设置传播级别
*/
Propagation propagation() default Propagation.REQUIRED;

/**
* 设置事务隔离级别
*/
Isolation isolation() default Isolation.DEFAULT;

/**
* 事务超时
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

/**
* 事务只读 默认是可以修改的 如果设置为true只能执行查询相关
*/
boolean readOnly() default false;

/**
* 指定错误回滚 当未抛出该类型的错误或者其子类的错误时 不会回滚 默认是任何错误(异常)都会回滚
*/
Class<? extends Throwable>[] rollbackFor() default {};

/**
* 略
*/
String[] rollbackForClassName() default {};

/**
* 如果配置这个 如果抛出此异常将不会回滚
*/
Class<? extends Throwable>[] noRollbackFor() default {};

/**
* 略
*/
String[] noRollbackForClassName() default {};

}

public enum Isolation {

/**
* 使用默认的隔离级别
* 所有其他级别对应于JDBC隔离级别
* 具体隔离级别 查看 java.sql.Connection
* Mysql 默认 RR(REPEATABLE_READ)
* Oracle 默认 RC(READ_COMMITTED)
*/
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

/**
* 未提交读 什么问题都不解决
* @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
*/
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

/**
* 提交读 解决脏读
* @see java.sql.Connection#TRANSACTION_READ_COMMITTED
*/
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

/**
* 可重复读 解决脏读和不可重复读 不解决幻读
*/
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

/**
* 串行读 解决所有的事务隔离问题
*/
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);


private final int value;


Isolation(int value) {
this.value = value;
}

public int value() {
return this.value;
}

}

传播行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public enum Propagation {

/**
* Support a current transaction, create a new one if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>This is the default setting of a transaction annotation.
* 外围有事务则加入形成同一个事务,外围无事务则新开启,内部事务之间相互独立
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

/**
* Support a current transaction, execute non-transactionally if none exists.
* Analogous to EJB transaction attribute of the same name.
* <p>Note: For transaction managers with transaction synchronization,
* {@code SUPPORTS} is slightly different from no transaction at all,
* as it defines a transaction scope that synchronization will apply for.
* As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
* will be shared for the entire specified scope. Note that this depends on
* the actual synchronization configuration of the transaction manager.
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
* 若外围没有事务则非事务执行,有事务则同 REQUIRED
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

/**
* Support a current transaction, throw an exception if none exists.
* Analogous to EJB transaction attribute of the same name.
* 使用外围事务,若外围无事务则抛出异常
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

/**
* Create a new transaction, and suspend the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available to it (which is server-specific in standard Java EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
* 外围有无事务都开启新事务,相互独立,且与外围事务相互独立开 如果当前有事务,就把当前的事务挂起
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

/**
* Execute non-transactionally, suspend the current transaction if one exists.
* Analogous to EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available to it (which is server-specific in standard Java EE).
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
* 非事务执行,若外围存在事务则挂起该事务
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

/**
* Execute non-transactionally, throw an exception if a transaction exists.
* Analogous to EJB transaction attribute of the same name.
* 非事务执行,当外围有事务则抛出异常
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),

/**
* Execute within a nested transaction if a current transaction exists,
* behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB.
* <p>Note: Actual creation of a nested transaction will only work on specific
* transaction managers. Out of the box, this only applies to the JDBC
* DataSourceTransactionManager. Some JTA providers might support nested
* transactions as well.
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
* 外围无事务,则同 REQUIRED 内部开启新事务相互独立。外围有事务,则内部事务是其子事务,主事务回滚则子事务全部回滚,子事务回滚不影响其他子事务和主事务
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);


private final int value;


Propagation(int value) {
this.value = value;
}

public int value() {
return this.value;
}

}

Spring 依赖注入概念和@Autowired 的用法。

1
2
3
4
5
6
7
8
9
概念:实例不再由程序员实例化,而是通过spring容器帮我们new指定实例并且将实例注入到需要该对象的类。
依赖注入能够让相互协作的软件组件保持松散耦合
@Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用来消除 set ,get方法。也可作用与集合上
这里授权服务配置类是继承了AuthorizationServerConfigurerAdapter,而AuthorizationServerConfigurerAdapter又实现了AuthorizationServerConfigurer接口!
源码AuthorizationServerConfigurer
@Autowired对List自动注入
//@Autowired注解用在接口的集合上面,所有实现该接口的实现类都会在该集合中
@Autowired(required = false)
private List<IAsynTask> tasks = Collections.emptyList();

Spring Bean 的生命周期。

Bean 的生命周期概括起来就是 4 个阶段:

实例化(Instantiation)
属性赋值(Populate)
初始化(Initialization)
销毁(Destruction)
流程

Spring Boot 启动流程以及底层源码

流程

索引的数据结构(比如 B+树)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
B+Tree
B+Tree相对于B-Tree有几点不同:
非叶子节点只存储键值信息。
所有叶子节点之间都有一个链指针。
数据记录都存放在叶子节点中。
查询速度快,但是占用空间
索引结构:B-Tree B+Tree B:balance
B-Tree:平衡二叉树
特点:
1.具有数据节点
2.指向下层指针
3.指向数据指针
缺页查询,产生IO
B+Tree:
特点:
1.具有数据节点
2.指向下层指针
命中数据3层查找后查询数据指针
加载更快,产生更少IO
效率:BTree更高,但从IO角度,Mysql选择B+Tree

Hash 索引的特点


Hash 索引只能够用于使用 = 或者 <=> 运算符的相等比较(但是速度更快)。Hash 索引不能够用于诸如 < 等
用于查找一个范围值的比较运算符。依赖于这种单值查找的系统被称为 “键-值存储”;对于这种系统,尽可能地使用 hash 索引。
优化器不能够使用 hash 索引来加速 ORDER BY 操作。这种类型的索引不能够用于按照顺序查找下一个条目。
MySql 无法使用 hash 索引估计两个值之间有多少行(这种情况由范围优化器来决定使用哪个索引)。如果你将一张 MyISAM 或 InnoDB 表转换成一个 hash 索引的内存表时,一些查询可能会受此影响。
查找某行记录必须进行全键匹配。而 B-tree 索引,任何该键的左前缀都可用以查找记录

索引是为了加速对表中数据行的检索而创建的一种分散的存储结构
建索引的语句

1
CREATE INDEX idx_xxx USING BTREE ON tablename (字段,字段,字段);

索引的种类尤其是复合索引以及对应的回表和最左匹配原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
普通索引:最基本的索引,没有任何约束限制。
唯一索引:和普通索引类似,但是具有唯一性约束,可以有 null
主键索引:特殊的唯一索引,不允许有 null,一张表最多一个主键索引
组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
全文索引:对文本的内容进行分词、搜索
覆盖索引:查询列要被所建的索引覆盖,不必读取数据行
1、复合索引绑定的第一个列,没有出现在查询条件中;
举例说明:为emp表插入索引idx_age_deptid_name(age,deptid,name),但是在查询条件中未使用age,导致复合索引全部失效。

2、复合索引绑定的多个列是有顺序的,某一个列没有出现在查询条件中,存储引擎不能使用索引中该列及其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里没有deptid列,会导致复合索引中的deptid及其后的索引失效。

3.查询条件中出现某个列是范围查询的,存储引擎不能使用复合索引中该列其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里deptid列使用到了范围查询,会导致复合索引中的deptid其后的索引失效。

4.查询条件中某列使用否定条件的(!= <> IS NOT NULL),存储引擎不能使用索引中该列其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里deptid列使用到了否定条件,会导致复合索引中的deptid其后的索引失效。

5.查询条件中某列使用LIKE条件后的字段是以%开头的(如:’%ABC’),存储引擎不能使用索引中该列及其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里name列使用到了like ‘%a’,会导致复合索引中的name及其后的索引失效。

6.查询条件中某列使用函数的,存储引擎不能使用索引中该列及其后的所有列。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件里name列使用到了like ‘%a’,会导致复合索引中的name及其后的索引失效。

7.查询条件中某列使用类型转换的(包括显示的和隐示的),存储引擎不能使用索引中该列及其后的所有列。
如:字符串类型的列NAME=3,就是隐示的类型转换,将INT型转换为字符串类型。如果写为NAME=’3’,就不是类型转换。
举例:为emp表插入索引idx_age_deptid_name(age,deptid,name),查询时查询条件name=3,会导致复合索引中的name及其后的索引失效。条件写成name=‘3’,索引就不会失效。

回表

如果索引的列在 select 所需获得的列中(因为在 mysql 中索引是根据索引列的值进行排序的,所以索引节点中存在该列中的部分值)或者根据一次索引查询就能获得记录就不需要回表,如果 select 所需获得列中有大量的非索引列,索引就需要到表中找到相应的列的信息,这就叫回表。
使用聚集索引(主键或第一个唯一索引)就不会回表,普通索引就会回表

索引下推优化,

可以在索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表字数。
最左匹配原则
带头大哥不能死,中间兄弟不能断

Spring AOP 底层原理

AOP 底层是采用动态代理机制实现的:接口+实现类

如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代

理对象。

没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用

Cglib 生成一个被代理对象的子类来作为代理。
就是由代理创建出一个和 impl 实现类平级的一个对象,但是这个对象不是一个真正的对象,
只是一个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这
样就不需要修改源代码。

HashMap在java1.7之前底层数据结构是数组+链表,1.8之后是数组+链表+红黑树,
在1.7以前的put方法采用的是头插法,当hash碰撞次数到达8,且桶内元素到达64个的时候形成链表,但是在极端情况下会造成链表过长,效率变低,并且在rehash的时候,头插法会造成回环链首尾相连,形成死锁,在java1.8以后采用红黑树,除了添加效率都高,是线程不安全的,不安全示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class HashMapTest {

public static void main(String[] args) {
HashMapThread thread0 = new HashMapThread();
HashMapThread thread1 = new HashMapThread();
HashMapThread thread2 = new HashMapThread();
HashMapThread thread3 = new HashMapThread();
HashMapThread thread4 = new HashMapThread();
thread0.start();
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}

class HashMapThread extends Thread {
private static AtomicInteger ai = new AtomicInteger();
private static Map<Integer, Integer> map = new HashMap<>();

@Override
public void run() {
while (ai.get() < 1000000) {
map.put(ai.get(), ai.get());
ai.incrementAndGet();
}
}
}

JDK1.8 之前

JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。 ## HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n -

  1. & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在
    元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,
    直接覆盖,不相同就通过拉链法解决冲突。
    所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了
    防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

    JDK1.8 之后

    当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据
    HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于 64 的情况下,才会
    执行转换红黑树操作,以减少搜索时间。否则,就是只是执行 resize() 方法对数组扩容。

1.通常代替HashMap的安全由HashTable代替,但是多线程下他的put.get方法都是synchronized,效率太低,
2.Collections.synchronizedMap(),底层仍是synchronized
3.java9实现Collections.of()
ConcurrentHashMap 与 ConcurrentSkipListMap
ConcurrentHashMap 加锁
ConcurrentSkipListMap 不需要加锁,浪费空间,
4.ConcurrentHashMap
ConcurrentHashMap如何保证线程安全,在1.7以前由划分segment分段锁机制,共计16个并发级别,隔离级别太大,有很多空间就浪费了,太小就段内的元素过多
1.8以后是cas算法C语言写得,无锁算法,put添加的时候,链表+红黑树
put方法(无锁添加)

3、HashMap 的扩容机制是怎样的?

一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的 2 倍。
HashMap 的容量是有上限的,必须小于 1<<30,即 1073741824。如果容量超出了这个
数,则不再增长,且阈值会被设置为 Integer.MAX_VALUE。
JDK7 中的扩容机制
空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数
组。
有参构造函数:根据参数确定容量、负载因子、阈值等。
第一次 put 时会初始化数组,其容量变为不小于指定容量的 2 的幂数,然后根据负载因子
确定阈值。
如果不是第一次扩容,则 新容量=旧容量 x 2 ,新阈值=新容量 x 负载因子 。
JDK8 的扩容机制
空参数的构造函数:实例化的 HashMap 默认内部数组是 null,即没有实例化。第一次调
用 put 方法时,则会开始第一次初始化扩容,长度为 16。 ## 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的 2 的幂数,将
这个数设置赋值给阈值(threshold)。第一次调用 put 方法时,会将阈值赋值给容量,
然后让 阈值 = 容量 x 负载因子。 ## 如果不是第一次扩容,则容量变为原来的 2 倍,阈值也变为原来的 2 倍。(容量和阈值都
变为原来的 2 倍时,负载因子还是不变)。
此外还有几个细节需要注意:
首次 put 时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;
不是首次 put,则不再初始化,直接存入数据,然后判断是否需要扩容;

4、ConcurrentHashMap 的存储结构是怎样的?

Java7 中 ConcurrnetHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个
线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它
的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变,默认 Segment 的
个数是 16 个。
Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由
Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红
黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红
黑树,在冲突小于一定数量时又退回链表。

5、线程池大小如何设置?

CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N (CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,
或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而
在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而
线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程
使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是
2N。

如何判断是 CPU 密集任务还是 IO 密集任务?

CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。单
凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相
比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
6、IO 密集=Ncpu*2 是怎么计算出来?

I/O 密集型任务任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理
I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在
I/O 密集型任务的应用中,我们可以多配置一些线程。例如:数据库交互,文件上传下
载,网络传输等。IO 密集型,即该任务需要大量的 IO,即大量的阻塞,故需要多配置线
程数。

7、G1 收集器有哪些特点?

G1 的全称是 Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它。
G1 GC 最主要的设计目标是:将 STW 停顿的时间和分布,变成可预期且可配置的。
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点:
并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者
CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程
执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。

分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留

了分代的概念。
空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法
实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共
同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明
确指定在一个长度为 M 毫秒的时间片段内。
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的
Region(这也就是它的名字 Garbage-First 的由来)

8、你有哪些手段来排查 OOM 的问题?

增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -
XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信
息到指定目录。
同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域。
使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未
清理,时间长了就会内存溢出,可以把改为弱引用。

9、请你谈谈 MySQL 事务隔离级别,MySQL 的默认隔离级别是什么?

为了达到事务的四大特性,数据库定义了 4 种不同的事务隔离级别:
READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许脏读,也就是可能读取
到其他会话中未提交事务修改的数据,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交): 只能读取到已经提交的数据。Oracle 等多数数据
库默认都是该级别 (不重复读),可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据是
被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化):最高的隔离级别,完全服从 ACID 的隔离级别。所有的事
务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏
读、不可重复读以及幻读。
MySQL 默认采用的 REPEATABLE_READ 隔离级别。

可重复读解决了哪些问题?

可重复读的核心就是一致性读(consistent read);保证多次读取同一个数据时,其值都和事
务开始时候的内容是一致,禁止读取到别的事务未提交的数据,会造成幻读。
而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就
需要进入锁等待。
32
查询只承认在事务启动前就已经提交完成的数据。
可重复读解决的是重复读的问题,可重复读在快照读的情况下是不会有幻读,但当前读的
时候会有幻读。

11、对 SQL 慢查询会考虑哪些优化 ?

分析语句,是否加载了不必要的字段/数据。
分析 SQL 执行计划(explain extended),思考可能的优化点,是否命中索引等。
查看 SQL 涉及的表结构和索引信息。
如果 SQL 很复杂,优化 SQL 结构。
按照可能的优化点执行表结构变更、增加索引、SQL 改写等操作。
查看优化后的执行时间和执行计划。
如果表数据量太大,考虑分表。
利用缓存,减少查询次数

12、谈一谈缓存穿透、缓存击穿和缓存雪崩,以及解决办法?

缓存穿透
问题:大量并发查询不存在的 KEY,在缓存和数据库中都不存在,同时给缓存和数据库带
来压力。
原因:一般而言,缓存穿透有 2 种可能性:业务数据被误删,导致缓存和数据库中都没有
数据。恶意进行 ddos 攻击。
分析:为什么会多次透传呢?不存在 一直为空,需要注意让缓存能够区分 KEY 不存在和
查询到一个空值。
解决办法:缓存空值的 KEY,这样第一次不存在也会被加载会记录,下次拿到有这个
KEY。Bloom 过滤或 RoaingBitmap 判断 KEY 是否存在,如果布隆过滤器中没有查到这
个数据,就不去数据库中查。在处理请求前增加恶意请求检查,如果检测到是恶意攻击,
则拒绝进行服务。完全以缓存为准,使用延迟异步加载的策略(异步线程负责维护缓存的
数据,定期或根据条件触发更新),这样就不会触发更新。
缓存击穿
问题:某个 KEY 失效的时候,正好有大量并发请求访问这个 KEY。 分析:跟穿透其实很像,属于比较偶然的。
解决办法:KEY 的更新操作添加全局互斥锁。完全以缓存为准,使用延迟异步加载的策略
(异步线程负责维护缓存的数据,定期或根据条件触发更新),这样就不会触发更新。
缓存雪崩
问题:当某一时刻发生大规模的缓存失效的情况,导致大量的请求无法获取数据,从而将
流量压力传导到数据库上,导致数据库压力过大甚至宕机。
原因:一般而言,缓存雪崩有 2 种可能性:大量的数据同一个时间失效:比如业务关系强
相关的数据要求同时失效 Redis 宕机
分析:一般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,可能会导致缓存
数据同一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合
适,数据要均匀分享,缓存服务器要多台高可用。
解决办法:更新策略在时间上做到比较平均。如果数据需要同一时间失效,可以给这批数
据加上一些随机值,使得这批数据不要在同一个时间过期,降低数据库的压力。使用的热
数据尽量分散到不同的机器上。多台机器做主从复制或者多副本,实现高可用。做好主从
的部署,当主节点挂掉后,能快速的使用从结点顶上。实现熔断限流机制,对系统进行负
载能力控制。对于非核心功能的业务,拒绝其请求,只允许核心功能业务访问数据库获取
数据。服务降价:提供默认返回值,或简单的提示信息。

13、LRU 是什么?如何实现?

最近最少使用策略 LRU(Least Recently Used)是一种缓存淘汰算法,是一种缓存淘汰机
制。
使用双向链表实现的队列,队列的最大容量为缓存的大小。在使用过程中,把最近使用的
页面移动到队列头,最近没有使用的页面将被放在队列尾的位置
使用一个哈希表,把页号作为键,把缓存在队列中的节点的地址作为值,只需要把这个页
对应的节点移动到队列的前面,如果需要的页面在内存中,此时需要把这个页面加载到内
存中,简单的说,就是将一个新节点添加到队列前面,并在哈希表中跟新相应的节点地
址,如果队列是满的,那么就从队尾移除一个节点,并将新节点添加到队列的前面。

14、什么是堆内存?参数如何设置?

堆内存是指由程序代码自由分配的内存,与栈内存作区分。
在 Java 中,堆内存主要用于分配对象的存储空间,只要拿到对象引用,所有线程都可
以访问堆内存。
-Xmx, 指定最大堆内存。 如 -Xmx4g. 这只是限制了 Heap 部分的最大值为 4g。这个内
存不包括栈内存,也不包括堆外使用的内存。
-Xms, 指定堆内存空间的初始大小。 如 -Xms4g。 而且指定的内存大小,并不是操作系
统实际分配的初始值,而是 GC 先规划好,用到才分配。 专用服务器上需要保持 –Xms
和 –Xmx 一致,否则应用刚启动可能就有好几个 FullGC。当两者配置不一致时,堆内存
扩容可能会导致性能抖动。
34
-Xmn, 等价于 -XX:NewSize,使用 G1 垃圾收集器 不应该 设置该选项,在其他的某些业
务场景下可以设置。官方建议设置为 -Xmx 的 1/2 ~ 1/4.
-XX:MaxPermSize=size, 这是 JDK1.7 之前使用的。Java8 默认允许的 Meta 空间无限
大,此参数无效。
-XX:MaxMetaspaceSize=size, Java8 默认不限制 Meta 空间, 一般不允许设置该选
项。
-XX:MaxDirectMemorySize=size,系统可以使用的最大堆外内存,这个参数跟 -
Dsun.nio.MaxDirectMemorySize 效果相同。
-Xss, 设置每个线程栈的字节数。 例如 -Xss1m 指定线程栈为 1MB,与-
XX:ThreadStackSize=1m 等价

15、栈和队列,举个使用场景例子?

栈(后进先出)可以用于字符匹配,数据反转等场景
队列(先进先出)可以用于任务队列,共享打印机等场景

16、MySQL 为什么 InnoDB 是默认引擎?

MyISAM与InnoDB 的区别(9个不同点)
区别:

  1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;

  2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;

  3. InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。

    MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
    
    也就是说:InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。
    
  4. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件);

那么为什么InnoDB没有了这个变量呢?

因为InnoDB的事务特性,在同一时刻表中的行数对于不同的事务而言是不一样的,因此count统计会计算对于当前事务而言可以统计到的行数,而不是将总行数储存起来方便快速查询。InnoDB会尝试遍历一个尽可能小的索引除非优化器提示使用别的索引。如果二级索引不存在,InnoDB还会尝试去遍历其他聚簇索引。
如果索引并没有完全处于InnoDB维护的缓冲区(Buffer Pool)中,count操作会比较费时。可以建立一个记录总行数的表并让你的程序在INSERT/DELETE时更新对应的数据。和上面提到的问题一样,如果此时存在多个事务的话这种方案也不太好用。如果得到大致的行数值已经足够满足需求可以尝试SHOW TABLE STATUS
  1. Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引领域的查询效率上MyISAM速度更快高;PS:5.7以后的InnoDB支持全文索引了

  2. MyISAM表格可以被压缩后进行查询操作

  3. InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁

    InnoDB的行锁是实现在索引上的,而不是锁在物理行记录上。潜台词是,如果访问没有命中索引,也无法使用行锁,将要退化为表锁。
    

例如:

t_user(uid, uname, age, sex) innodb;

uid PK
无其他索引
update t_user set age=10 where uid=1;             命中索引,行锁。

update t_user set age=10 where uid != 1;           未命中索引,表锁。

update t_user set age=10 where name='chackca';    无索引,表锁。

8、InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键),而Myisam可以没有

9、Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI

    Innodb:frm是表定义文件,ibd是数据文件

    Myisam:frm是表定义文件,myd是数据文件,myi是索引文件

如何选择:
1. 是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;

2. 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,请使用InnoDB。

3. 系统奔溃后,MyISAM恢复起来更困难,能否接受;

4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。

InnoDB为什么推荐使用自增ID作为主键?

答:自增ID可以保证每次插入时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对比使用UUID)。如果使用字符串主键和随机主键,会使得数据随机插入,效率比较差。

innodb引擎的4大特性

   插入缓冲(insert buffer),二次写(double write),自适应哈希索引(ahi),预读(read ahead)

19、MVCC 是什么?它的底层原理是什么?

MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并
发性能的一种机制。
事务版本号
表的隐藏列
undo log
read view

20、undo log 具体怎么回滚事务 ?

举个例子:
对于 insert 类型的 sql,会在 undo log 中记录下方才你 insert 进来的数据的 ID,当你想
roll back 时,根据 ID 完成精准的删除。
对于 delete 类型的 sql,会在 undo log 中记录方才你删除的数据,当你回滚时会将删除
前的数据 insert 进去。
对于 update 类型的 sql,会在 undo log 中记录下修改前的数据,回滚时只需要反向
update 即可。
对于 select 类型的 sql,别费心了,select 不需要回滚。

22、索引失效的情况有哪些?

like 以%开头索引无效,当 like 以&结尾,索引有效。
or 语句前后没有同事使用索引,当且仅当 or 语句查询条件的前后列均为索引时,索引生
效。
组合索引,使用的不是第一列索引时候,索引失效,即最左匹配规则。
数据类型出现隐式转换,如 varchar 不加单引号的时候可能会自动转换为 int 类型,这个
时候索引失效。
在索引列上使用 IS NULL 或者 IS NOT NULL 时候,索引失效,因为索引是不索引空值
得。
在索引字段上使用,NOT、 <>、!= 、时候是不会使用索引的,对于这样的处理只会进
行全表扫描。
对索引字段进行计算操作,函数操作时不会使用索引。
当全表扫描速度比索引速度快的时候不会使用索引。
索引失效场景一:带头大哥不能死,中间兄弟不能断

索引失效场景二:在索引列上做操作

索引失效场景三:范围条件右边全失效

索引低效场景四:select * 会降低索引的效率

索引失效场景五:使用!=或<>会导致索引失效

索引失效场景六、isnull和is not null字段无法使用索引

索引失效场景七、%like%查询时的索引失效问题

索引失效场景八、字符串不加单引号索引失效

索引失效场景九、少用or,用or连接索引会失效

Spring Bean 容器的生命周期是什么样的?

Bean 容器找到配置文件中 Spring Bean 的定义。
Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
如果涉及到一些属性值 利用 set()方法设置一些属性值。
如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入 Bean 的名
字。
如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传
入 ClassLoader 对象的实例。
如果 Bean 实现了 BeanFactoryAware 接口,调用 setBeanFactory()方法,传入
BeanFactory 对象的实例。
与上面的类似,如果实现了其他 *.Aware 接口,就调用相应的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessBeforeInitialization() 方法
39
如果 Bean 实现了 InitializingBean 接口,执行 afterPropertiesSet()方法。
如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行
postProcessAfterInitialization() 方法
当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方
法。
当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执
行指定的方法。

Redis 数据结构 压缩列表和跳跃表的区别

压缩列表(ziplist)本质上就是一个字节数组,是 Redis 为了节约内存而设计的一种线性
数据结构,可以包含多个元素,每个元素可以是一个字节数组或一个整数。
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指
针,从而达到快速访问节点的目的。跳跃表支持平均 O(logN)、最坏 O(N)复杂度的
节点查找,还可以通过顺序性操作来批量处理节点

1.redis的hash怎么实现的?(实现原理)rehash过程

redis初始创建hash表,有序集合,链表时, 存储结构采用一种ziplist的存储结构, 这种结构内存排列更紧密, 能提高访存性能.
hash_max_ziplist_entries和hash_max_ziplist_value值作为阀值,hash_max_ziplist_entries表示一旦ziplist中元素数量超过该值,则需要转换为dict结构;hash_max_ziplist_value表示一旦ziplist中数据长度大于该值,则需要转换为dict结构。
哈希等价于Java语言的HashMap或者是Python语言的字典(Dict)
redis hash 的内部结构.第一维是数组,第二维是链表.组成一个 hashtable.
在 Java 中 HashMap 扩容是个很耗时的操作,需要去申请新的数组,为了追求高性能,Redis 采用了渐进式 rehash 策略.这也是 hash 中最重要的部分.
在扩容的时候 rehash 策略会保留新旧两个 hashtable 结构,查询时也会同时查询两个 hashtable.Redis会将旧 hashtable 中的内容一点一点的迁移到新的 hashtable 中,当迁移完成时,就会用新的 hashtable 取代之前的.当 hashtable 移除了最后一个元素之后,这个数据结构将会被删除.
https://juejin.im/post/5cfe6383e51d45599e019d8f
与java的hashmap的rehash区别
个人理解:hashmap的rehash是一次性拷贝的,不同的是,Redis的字典只能是字符串,另外他们rehash的方式不一样,因为Java的HashMap的字典很大时,rehash是个耗时的操作,需要一次全部rehash。Redis为了追求高性能,不能堵塞服务,所以采用了渐进式rehash策略。
rehash的详细步骤
https://www.cnblogs.com/meituantech/p/9376472.html
与ConcurrentHashMap扩容的策略比较?
ConcurrentHashMap采用的扩容策略为: “多线程协同式rehash“。
1.扩容所花费的时间对比: 一个单线程渐进扩容,一个多线程协同扩容。在平均的情况下,是ConcurrentHashMap 快。这也意味着,扩容时所需要 花费的空间能够更快的进行释放。
2.读操作,两者性能相差不多。
3.写操作,Redis的字典返回更快些,因为它不像ConcurrentHashMap那样去帮着扩容(当要写的桶位已经搬到了newTable时),等扩容完才能进行操作。
4.删除操作,与写一样。
http://xytschool.com/resource/236.html

redis如何保证高可用

保证redis高可用机制需要redis主从复制、redis持久化机制、哨兵机制、keepalived等的支持。
主从复制的作用:数据备份、读写分离、分布式集群、实现高可用、宕机容错机制等。

redis主从复制原理

首先主从复制需要分为两个角色:master(主) 和 slave(从) ,注意:redis里面只支持一个主,不像Mysql、Nginx主从复制可以多主多从。

(1)redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

(2)通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

https://blog.csdn.net/itcats_cn/article/details/82428716

说说redis的持久化机制,为啥不能用redis做专门的持久化数据库存储?

个人理解:强一致性的数据是不适合放在缓存中的。另外MySQL对事务的支持也是redis本身不能达到的,需要单独实现
一般不是说redis or MySQL,而是redis+MySQL
https://blog.csdn.net/u011784767/article/details/76824822
为什么Redis进行RDB持久化数据时,新起一个进程而不是在原进程中起一个线程来持久化数据
(1)Redis RDB持久化机制会阻塞主进程,这样主进程就无法响应客户端请求。
(2)我们知道Redis对客户端响应请求的工作模型是单进程和单线程的,如果在主进程内启动一个线程,这样会造成对数据的竞争条件,为了避免使用锁降低性能。基于以上两点这就是为什么Redis通过启动一个进程来执行RDB了
—单线程的redis为什么这么快
(1)纯内存操作
(2)单线程操作,避免了频繁的上下文切换
(3)采用了非阻塞I/O多路复用机制

1

Redis的数据类型以及使用场景

(1)String
这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。
一般做一些复杂的计数功能的缓存。

(2)hash
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,
就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

(3)list
使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,
做基于redis的分页功能,性能极佳,用户体验好。

(4)set
因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?
因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再启一个公共服务,太麻烦了。

另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

(5)sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。sorted set可以用来做延时任务。最后一个应用就是可以做范围查找

redis的过期策略以及内存淘汰机制

redis采用的是定期删除+惰性删除+内存淘汰策略。
[2020年6月29日17:25:36在平时的项目中测试,不定期会产生无用token的key数据,平时可以进行模糊删除]

缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

解决方案:
(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,
异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。
迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。

缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

解决方案:
(一)给缓存的失效时间,加上一个随机值,避免集体失效。
(二)使用互斥锁,但是该方案吞吐量明显下降了。
(三)双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。
自己做缓存预热操作。然后细分以下几个小点
1 从缓存A读数据库,有则直接返回
2 A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
3 更新线程同时更新缓存A和缓存B。

如何解决redis的并发竞争key问题

分析:这个问题大致就是,同时有多个子系统去set一个key。这个时候要注意什么呢?大家思考过么。
需要说明一下,博主提前百度了一下,发现答案基本都是推荐用redis事务机制。博主不推荐使用redis的事务机制。
因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,
这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。

回答:如下所示
(1)如果对这个key操作,不要求顺序
这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。
(2)如果对这个key操作,要求顺序
假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.
期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。这种时候我们在数据写入数据库的时候,
需要保存一个时间戳。假设时间戳如下
系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。
redis分页
HSCAN testHash “0” count 10

注:测试field数量在22条时(没有测试Redis中Hash使分页生效时的field数量的下限),分页未生效。
#mysql 执行一个 sql 的过程
执行完毕之后有一个缓存的过程
https://www.cnblogs.com/luoying/p/12073812.html

MySQL分页limit速度太慢的优化方法

1.子查询优化法
先找出第一条数据,然后大于等于这条数据的id就是要获取的数据
缺点:数据必须是连续的,可以说不能有where条件,where条件会筛选数据,导致数据失去连续性
2.limit限制优化法
把limit偏移量限制低于某个数
3.where条件先过滤后分页

wait notify 为什么要搭配使用?

单独调用会报异常
只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。因为程序验证通常是在对象的同步方法或同步代码块中调用它们的。如果尝试在未获取对象锁时调用这三个方法,
“java.lang.IllegalMonitorStateException:current thread not owner”。
底层把对象作为一个监视器

栈会溢出吗?什么时候溢出?方法区会溢出吗?

栈是线程私有的,它的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来
存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,
对象引用类型。如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出
StackOverflowError 异常,方法递归调用产生这种结果。如果 Java 虚拟机栈可以动态扩展,
并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时
候没有足够的内存去创建对应的虚拟机栈,那么 Java 虚拟机将抛出一个 OutOfMemory 异
常。(线程启动过多)。
方法区会发生溢出。
HotSpot jdk1.7 之前字符串常量池是方法区的一部分,方法区叫做“永久代”,在 1.7 之前
无限的创建对象就会造成内存溢出,提示信息:PermGen space 而是用 jdk1.7 之后,开始逐
步去永久代,就不会产生内存溢出。
方法区用于存放 Class 的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等,
如果动态生成大量的 Class 文件,也会产生内存溢出。常见的场景还有:大量 JSP 或动态产生
JSP 文件的应用(JSP 第一次运行时需要编译为 java 类)、基于 OSGi 的应用(即使是同一个
类文件,被不同的类加载器加载也会视为不同的类)

redis排行榜代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public Object countPoint(Long pointId,String ponitList, Long userId) {
String key = RedisConstants.get(pointId.toString());
String userIdStr = userId.toString();
boolean flag = redisService.isMember(key, userIdStr);
DetailVo vo = DetailVo.builder()
.pointId(pointId)
.likeCreateTime(new Date())
.likeUserId(userId)
.likedUserId(likedUserId)
.build();
Map resultMap = new HashMap();
if (flag) {
redisService.srem(key, userIdStr);
vo.setType(2);
//减分
redisService.incrScoreZset(ponitList,likedUserId.toString(),-1);
resultMap.put("no", 0);
} else {
redisService.sadd(key, userIdStr);
//加分
redisService.incrScoreZset(ponitList,likedUserId.toString(),1);
vo.setType(1);
resultMap.put("yes", 1);
}
//发送消息
mQProducer.sendUpdateUp(vo);
resultMap.put("Number", redisService.scard(key));
return resultMap;
}

请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;
接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的
参数,就是传递给 sql 的参数。
Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符
串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个
<select>、<insert>、<update>、<delete>标签,都会被解析为一个
MapperStatement 对象。
举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯
一找到 namespace 为 com.mybatis3.mappers.StudentDao 下面 id 为
findStudentById 的 MapperStatement。
Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻
找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK
动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而
执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。

Mybatis 是如何进行分页的?分页插件的原理是什么?

Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内
存分页,而非物理分页。可以在 sql 内直接书写带有物理分页的参数来完成物理分
页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件
的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物
理分页语句和物理分页参数。

Mybatis 的一级、二级缓存

1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为
Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就
将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap
存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,
如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要
实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置

3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存
Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将
被 clear。

Redis面试专题

redis 和 memcached 什么区别?为什么高并发下有时单线程的 redis 比多线程的

memcached 效率要高?
区别:
1.mc 可缓存图片和视频。rd 支持除 k/v 更多的数据结构;
2.rd 可以使用虚拟内存,rd 可持久化和 aof 灾难恢复,rd 通过主从支持数据备份;
3.rd 可以做消息队列。
原因:mc 多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。

假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。
对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问
题?
这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一
段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指
令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客
户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

Synchronized 锁升级过程

首先,synchronized 是什么?我们需要明确的给个定义——同步锁,没错,它就是把锁。

可以用来干嘛?锁,当然当然是用于线程间的同步,以及保护临界区内的资源。我们知道,锁是个非常笼统的概念,像生活中有指纹锁、密码锁等等多个种类,那 synchronized 代表的锁具体是把什么锁呢?

答案是—— Java 内置锁。在 Java 中,每个对象中都隐藏着一把锁,而 synchronized 关键字就是激活这把隐式锁的把手(开关)。

先来简单了解一下 synchronized,我们知道其共有 3 种使用方式:

关于扫描时markword,可以去了解下对象在JVM中的结构,这里简单说明,每个对象都会有一个对象头markword,这块区域可以存放hashcode和锁信息以及GC信息

Synchronized 的使用

修饰静态方法:锁住当前 class,作用于该 class 的所有实例
修饰非静态方法:只会锁住当前 class 的实例
修饰代码块:该方法接受一个对象作为参数,锁住的即该对象
使用方法就不在这里赘述,可自行搜索其详细的用法,这不是本篇文章所关心的内容。

知道了 synchronized 的概念,回头来看标题,它说的锁升级到底是个啥?对于不太熟悉锁升级的人来说,可能会想:

所谓锁,不就是啪一下锁上就完事了吗?升级是个什么玩意?这跟打扑克牌也没关系啊。

对于熟悉的人来说,可能会想:

不就是「无锁 ==> 偏向锁 ==> 轻量级锁 ==> 重量级锁 」吗?

你可能在很多地方看到过上面描述的锁升级过程,也能直接背下来。但你真的知道无锁、偏向锁、轻量级锁、重量级锁到底代表着什么吗?这些锁存储在哪里?以及什么情况下会使得锁向下一个 level 升级?

想知道答案,我们似乎必须先搞清楚 Java 内置锁,其内部结构是啥样的?内置锁又存放在哪里?

答案在开篇提到过——在 Java 对象中。

那么现在的问题就从「内置锁结构是啥」变成了「Java 对象长啥样」。

对象结构
从宏观上看,Java 对象的结构很简单,分为三部分:

Java 对象结构

从微观上看,各个部分都还可以深入展开,详见下图:

Java 详细对象结构

接下来分别深入讨论一下这三部分。

对象头
从脑图中可以看出,其由 Mark Word、Class Pointer、数组长度三个字段组成。简单来说:

Mark Word:主要用于存储自身运行时数据
Class Pointer:是指针,指向方法区中该 class 的对象,JVM 通过此字段来判断当前对象是哪个类的实例
数组长度:当且仅当对象是数组时才会有该字段
Class Pointer 和数组长度没什么好说的,接下来重点聊聊 Mark Word。

Mark Word 所代表的「运行时数据」主要用来表示当前 Java 对象的线程锁状态以及 GC 的标志。而线程锁状态分别就是无锁、偏向锁、轻量级锁、重量级锁。

所以前文提到的这 4 个状态,其实就是 Java 内置锁的不同状态。

在 JDK 1.6 之前,内置锁都是重量级锁,效率低下。效率低下表现在

而在 JDK 1.6 之后为了提高 synchronized 的效率,才引入了偏向锁、轻量级锁。

随着锁竞争逐渐激烈,其状态会按照「无锁 ==> 偏向锁 ==> 轻量级锁 ==> 重量级锁 」这个方向逐渐升级,并且不可逆,只能进行锁升级,而无法进行锁降级。

接下来我们思考一个问题,既然 Mark Word 可以表示 4 种不同的锁状态,其内部到底是怎么区分的呢?(由于目前主流的 JVM 都是 64 位,所以我们只讨论 64 位的 Mark Word)接下来我们通过图片直观的感受一下。

(1)无锁

无锁

这个可以理解为单线程很快乐的运行,没有其他的线程来和其竞争。

(2)偏向锁

偏向锁

首先,什么叫偏向锁?举个例子,一段同步的代码,一直只被线程 A 访问,既然没有其他的线程来竞争,每次都要获取锁岂不是浪费资源?所以这种情况下线程 A 就会自动进入偏向锁的状态。

后续线程 A 再次访问同步代码时,不需要做任何的 check,直接执行(对该线程的「偏爱」),这样降低了获取锁的代价,提升了效率。

看到这里,你会发现无锁、偏向锁的 lock 标志位是一样的,即都是 01,这是因为无锁、偏向锁是靠字段 biased_lock 来区分的,0 代表没有使用偏向锁,1 代表启用了偏向锁。为什么要这么搞?你可以理解为无锁、偏向锁在本质上都可以理解为无锁(参考上面提到的线程 A 的状态),所以 lock 的标志位都是 01 是没毛病的。

PS:这里的线程 ID 是持有当前对象偏向锁的线程

(3)轻量级锁

轻量级锁

但是,一旦有第二个线程参与竞争,就会立即膨胀为轻量级锁。企图抢占的线程一开始会使用自旋:

的方式去尝试获取锁。如果循环几次,其他的线程释放了锁,就不需要进行用户态到内核态的切换。虽然如此,但自旋需要占用很多 CPU 的资源(自行理解汽车空档疯狂踩油门)。如果另一个线程 一直不释放锁,难道它就在这一直空转下去吗?

当然不可能,JDK 1.7 之前是普通自旋,会设定一个最大的自旋次数,默认是 10 次,超过这个阈值就停止自旋。JDK 1.7 之后,引入了适应性自旋。简单来说就是:这次自旋获取到锁了,自旋的次数就会增加;这次自旋没拿到锁,自旋的次数就会减少。

(4)重量级锁

重量级锁

上面提到,试图抢占的线程自旋达到阈值,就会停止自旋,那么此时锁就会膨胀成重量级锁。当其膨胀成重量级锁后,其他竞争的线程进来就不会自旋了,而是直接阻塞等待,并且 Mark Word 中的内容会变成一个监视器(monitor)对象,用来统一管理排队的线程。

这个 monitor 对象,每个对象都会关联一个。monitor 对象本质上是一个同步机制,保证了同时只有一个线程能够进入临界区,在 HotSpot 的虚拟机中,是由 C++ 类 ObjectMonitor 实现的。

那么 monitor 对象具体是如何来管理线程的?接下来我们看几个 ObjectMonitor 类关键的属性:

ContentionQueue:是个队列,所有竞争锁的线程都会先进入这个队列中,可以理解为线程的统一入口,进入的线程会阻塞。
EntryList:ContentionQueue 中有资格的线程会被移动到这里,相当于进行一轮初筛,进入的线程会阻塞。
Owner:拥有当前 monitor 对象的线程,即 —— 持有锁的那个线程。
OnDeck:与 Owner 线程进行竞争的线程,同一时刻只会有一个 OnDeck 线程在竞争。
WaitSet:当 Owner 线程调用 wait() 方法被阻塞之后,会被放到这里。当其被唤醒之后,会重新进入 EntryList 当中,这个集合的线程都会阻塞。
Count:用于实现可重入锁,synchronized 是可重入的。
对象体
对象体包含了当前对象的字段和值,在业务中u l是较为核心的部分。

对齐字节
就是单纯用于填充的字节,没有其他的业务含义。其目的是为了保证对象所占用的内存大小为 8 的倍数,因为HotSpot VM 的内存管理要求对象的起始地址必须是 8 的倍数。

锁升级
了解完 4 种锁状态之后,我们就可以整体的来看一下锁升级的过程了。
锁的详细升级过程
1.一开始对象是无锁状态的

2.一个线程尝试执行Synchronize代码块时,成功获得对象的锁,通过CAS操作往该对象markword中插入当前线程id, 同时修改偏向锁的标志位 。此时是偏向锁(偏向这个线程的锁,锁计数+1),同一个线程可以重复进入该锁,锁计数+1,执行完毕会锁计数-1,直到锁计数复0,释放锁。

正是因为有记录线程id,所以Synchronized实现了可重入锁的逻辑(简单说就是一个锁的拥有者可以重复的获取自己的锁,而不会产生阻塞问题)
关于扫描时markword,可以去了解下对象在JVM中的结构,这里简单说明,每个对象都会有一个对象头markword,这块区域可以存放hashcode和锁信息以及GC信息

线程 A 进入 synchronized 开始抢锁,JVM 会判断当前是否是偏向锁的状态,如果是就会根据 Mark Word 中存储的线程 ID 来判断,当前线程 A 是否就是持有偏向锁的线程。如果是,则忽略 check,线程 A 直接执行临界区内的代码。

但如果 Mark Word 里的线程不是线程 A,就会通过自旋尝试获取锁,如果获取到了,就将 Mark Word 中的线程 ID 改为自己的;如果竞争失败,就会立马撤销偏向锁,膨胀为轻量级锁。

后续的竞争线程都会通过自旋来尝试获取锁,如果自旋成功那么锁的状态仍然是轻量级锁。然而如果竞争失败,锁会膨胀为重量级锁,后续等待的竞争的线程都会被阻塞。

无锁状态、偏向锁、轻量级锁、重量级锁 ,这是锁膨胀的过程,不可逆,但只有偏向锁可以变回无锁态。
转载博文:
https://cloud.tencent.com/developer/article/2074879#:~:text=%E8%AF%A6%E7%BB%86%E4%BA%86%E8%A7%A3%20Synchronized%20%E9%94%81%E5%8D%87%E7%BA%A7%E8%BF%87%E7%A8%8B%201%20%E4%BF%AE%E9%A5%B0%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%EF%BC%9A%E9%94%81%E4%BD%8F%E5%BD%93%E5%89%8D%20class%EF%BC%8C%E4%BD%9C%E7%94%A8%E4%BA%8E%E8%AF%A5%20class,%E7%9A%84%E6%89%80%E6%9C%89%E5%AE%9E%E4%BE%8B%202%20%E4%BF%AE%E9%A5%B0%E9%9D%9E%E9%9D%99%E6%80%81%E6%96%B9%E6%B3%95%EF%BC%9A%E5%8F%AA%E4%BC%9A%E9%94%81%E4%BD%8F%E5%BD%93%E5%89%8D%20class%20%E7%9A%84%E5%AE%9E%E4%BE%8B%203%20%E4%BF%AE%E9%A5%B0%E4%BB%A3%E7%A0%81%E5%9D%97%EF%BC%9A%E8%AF%A5%E6%96%B9%E6%B3%95%E6%8E%A5%E5%8F%97%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1%E4%BD%9C%E4%B8%BA%E5%8F%82%E6%95%B0%EF%BC%8C%E9%94%81%E4%BD%8F%E7%9A%84%E5%8D%B3%E8%AF%A5%E5%AF%B9%E8%B1%A1
锁优化篇:
JDK1.6引入了大量的优化,如:自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁。锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。但是有一点,不可以进行锁降级

一、自旋锁:
线程频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,会给系统带来很大的压力。同时很多锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。 所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁,如果释放了,就可以抢到锁。那怎么等待呢?其实就是执行一段无意义的循环,大家是不是瞬间觉得好low,原来就是执行一段for循环,别急着下结论,我们继续来分析

执行一段无意义的循环。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好。但是如果自旋很久都没抢到锁,那自旋就是浪费资源,说的难听点就是占着茅坑不拉屎。所以说,自旋等待的时间或者次数必须要有一个限度,如果超过了定义的时间仍然没有获取到锁,则把它挂起。

自旋锁在JDK 1.4.2中引入,默认关闭,但是可以使用-XX:+UseSpinning开启,在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整;但是无论你怎么调整这些参数,都无法满足不可预知的情况。于是JDK1.6引入自适应的自旋锁,让虚拟机会变得越来越聪明。

二、适应自旋锁
JDK 1.6引入了更加聪明的自旋锁,叫做自适应自旋锁。他的自旋次数是会变的,我用大白话来讲一下,就是线程如果上次自旋成功了,那么这次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么这次自旋也很有可能会再次成功。反之,如果某个锁很少有自旋成功,那么以后的自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。大家现在觉得没这么low了吧

三、锁消除
锁消除用大白话来讲,就是在一段程序里你用了锁,但是jvm检测到这段程序里不存在共享数据竞争问题,也就是变量没有逃逸出方法外,这个时候jvm就会把这个锁消除掉

我们程序员写代码的时候自然是知道哪里需要上锁,哪里不需要,但是有时候我们虽然没有显示使用锁,但是我们不小心使了一些线程安全的API时,如StringBuffer、Vector、HashTable等,这个时候会隐形的加锁。比如下段代码

复制
public void sbTest(){
StringBuffer sb= new StringBuffer();
for(int i = 0 ; i < 10 ; i++){
sb.append(i);
}
System.out.println(sb.toString());
}
复制
复制
上面这段代码,JVM可以明显检测到变量sb没有逃逸出方法sbTest()之外,所以JVM可以大胆地将sbTest内部的加锁操作消除。

四、锁粗化
众所周知在使用锁的时候,要让锁的作用范围尽量的小,这样是为了在锁内执行代码尽可能少,缩短持有锁的时间,其他等待锁的线程能尽快拿到锁。在大多数的情况下这样做是正确的。但是连续加锁解锁操作,可能会导致不必要的性能损耗,比如下面这个for循环:

锁粗化前:
for (…) {
synchronized (obj) {
// 一些操作
}
}
锁粗化后:
synchronized (this) {
for (…) {
// 一些操作
}
}
复制
复制
大家应该能看出锁粗化大概是什么意思了。就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。即加锁解锁操作会移到for循环之外。

五、偏向锁
当我们创建一个对象时,该对象的部分Markword关键数据如下。

bit fields

是否偏向锁

锁标志位

hash

0

01

从图中可以看出,偏向锁的标志位是“01”,状态是“0”,表示该对象还没有被加上偏向锁。(“1”是表示被加上偏向锁)。该对象被创建出来的那一刻,就有了偏向锁的标志位,这也说明了所有对象都是可偏向的,但所有对象的状态都为“0”,也同时说明所有被创建的对象的偏向锁并没有生效。

不过,当线程执行到临界区(critical section)时,此时会利用CAS(Compare and Swap)操作,将线程ID插入到Markword中,同时修改偏向锁的标志位。

所谓临界区,就是只允许一个线程进去执行操作的区域,即同步代码块。CAS是一个原子性操作

此时的Mark word的结构信息如下:

bit fields

是否偏向锁

锁标志位

threadId

epoch

1

01

此时偏向锁的状态为“1”,说明对象的偏向锁生效了,同时也可以看到,哪个线程获得了该对象的锁。

偏向锁是jdk1.6引入的一项锁优化,其中的“偏”是偏心的偏。它的意思就是说,这个锁会偏向于第一个获得它的线程,在接下来的执行过程中,假如该锁没有被其他线程所获取,没有其他线程来竞争该锁,那么持有偏向锁的线程将永远不需要进行同步操作。也就是说:在此线程之后的执行过程中,如果再次进入或者退出同一段同步块代码,并不再需要去进行加锁或者解锁操作,而是会做以下的步骤:

Load-and-test,也就是简单判断一下当前线程id是否与Markword当中的线程id是否一致.
如果一致,则说明此线程已经成功获得了锁,继续执行下面的代码.
如果不一致,则要检查一下对象是否还是可偏向,即“是否偏向锁”标志位的值。
如果还未偏向,则利用CAS操作来竞争锁,也即是第一次获取锁时的操作。
释放锁 偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。偏向锁的撤销需要等待全局安全点(这个时间点是上没有正在执行的代码)。其步骤如下:

暂停拥有偏向锁的线程,判断锁对象石是否还处于被锁定状态;
撤销偏向锁,恢复到无锁状态或者轻量级锁的状态;
安全点会导致stw(stop the word),导致性能下降,这种情况下应当禁用;

查看停顿–安全点停顿日志

要查看安全点停顿,可以打开安全点日志,通过设置JVM参数 -

XX:+PrintGCApplicationStoppedTime 会打出系统停止的时间,

添加-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 这两个参数会打印出详细信息,可以查看到使用偏向锁导致的停顿,时间非常短暂,但是争用严重的情况下,停顿次数也会非常多;

注意:安全点日志不能一直打开:

  1. 安全点日志默认输出到stdout,一是stdout日志的整洁性,二是stdout所重定向的文件如果不在/dev/shm,可能被锁。
  2. 对于一些很短的停顿,比如取消偏向锁,打印的消耗比停顿本身还大。
  3. 安全点日志是在安全点内打印的,本身加大了安全点的停顿时间。

所以安全日志应该只在问题排查时打开。
如果在生产系统上要打开,再再增加下面四个参数:
-XX:+UnlockDiagnosticVMOptions -XX: -DisplayVMOutput -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm.log
打开Diagnostic(只是开放了更多的flag可选,不会主动激活某个flag),关掉输出VM日志到stdout,输出到独立文件,/dev/shm目录(内存文件系统)。

此日志分三部分:
第一部分是时间戳,VM Operation的类型

第二部分是线程概况,被中括号括起来

total: 安全点里的总线程数
initially_running: 安全点时开始时正在运行状态的线程数
wait_to_block: 在VM Operation开始前需要等待其暂停的线程数

第三部分是到达安全点时的各个阶段以及执行操作所花的时间,其中最重要的是vmop

spin: 等待线程响应safepoint号召的时间;
block: 暂停所有线程所用的时间;
sync: 等于 spin+block,这是从开始到进入安全点所耗的时间,可用于判断进入安全点耗时;
cleanup: 清理所用时间;
vmop: 真正执行VM Operation的时间。
可见,那些很多但又很短的安全点,全都是RevokeBias, 高并发的应用会禁用掉偏向锁。

jvm开启/关闭偏向锁

开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking

六、轻量级锁
自旋锁的目标是降低线程切换的成本。如果锁竞争激烈,我们不得不依赖于重量级锁,让竞争失败的线程阻塞;如果完全没有实际的锁竞争,那么申请重量级锁都是浪费的。轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。

顾名思义,轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record(Lock Record:JVM检测到当前对象是无锁状态,则会在当前线程的栈帧中创建一个名为LOCKRECOD表空间用于copy Mark word 中的数据),如果更新成功,则轻量级锁获取成功,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。

当然,由于轻量级锁天然瞄准不存在锁竞争的场景,如果存在锁竞争但不激烈,仍然可以用自旋锁优化,自旋失败后再膨胀为重量级锁。

缺点:同自旋锁相似:如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁,那么维持轻量级锁的过程就成了浪费。

七、重量级锁
轻量级锁膨胀之后,就升级为重量级锁了。重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。
当轻量级所经过锁撤销等步骤升级为重量级锁之后,它的Markword部分数据大体如下

bit fields

锁标志位

指向Mutex的指针

10

为什么说重量级锁开销大呢

主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
这就是说为什么重量级线程开销很大的。

互斥锁(重量级锁)也称为阻塞同步、悲观锁

八、总结
偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁。

    一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个

线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将
对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。

    一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。


    轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

转载博文:https://cloud.tencent.com/developer/article/1698812?from=article.detail.2019347

Mysql json数据查询

1、使用 字段->’$.json属性’ 进行查询条件

2、使用 json_extract 函数查询,json_extract(字段, “$.json属性”)

3、根据json数组查询,用 JSON_CONTAINS(字段, JSON_OBJECT(‘json属性’, “内容”))

举例:

SELECT * from
test
– WHERE attributes -> ‘$.orderInviteCode.inviterUserId’ = 310000000780
– WHERE json_extract(attributes, “$.orderInviteCode.inviterUserId”) = 310000000780

Thread

Thread类有7个基本构造函数,当指定线程执行顺序时,可调用start方法,然后调用join方法,其中join的实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public final void join() throws InterruptedException {
join(0);
}




public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

java无法销毁一个线程,但是当调用isAlive方法时,返回false则已销毁
为什么放弃了stop方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
			 this method is inherently unsafe.  Stopping a thread with
* Thread.stop causes it to unlock all of the monitors that it
* has locked (as a natural consequence of the unchecked
* {@code ThreadDeath} exception propagating up the stack). If
* any of the objects previously protected by these monitors were in
* an inconsistent state, the damaged objects become visible to
* other threads, potentially resulting in arbitrary behavior. Many
* uses of {@code stop} should be replaced by code that simply
* modifies some variable to indicate that the target thread should
* stop running. The target thread should check this variable
* regularly, and return from its run method in an orderly fashion
* if the variable indicates that it is to stop running. If the
* target thread waits for long periods (on a condition variable,
* for example), the {@code interrupt} method should be used to
* interrupt the wait.
* For more information, see
* <a href="{@docRoot}/java.base/java/lang/doc-files/threadPrimitiveDeprecation.html">Why
* are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
防止死锁ddd
所以已经在1.2就过期了

说明Thread interrupt() isinterrupted()interrupted 的区别和含义
Thread.interrupt() 设置状态
isInterrupted() 判断 返回Boolean
interrupted 即判断又清除

yield

1
2
首先是一个native方法
public static native void yield();

向调度程序提示当前线程愿意让步,使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了.

{% if post.top %} 置顶 | {% endif %}