一、mysql使用innodb引擎,请简述mysql索引的最左前缀如何优化orderby语句?
关键点:
(1)如果排序字段不在索引列上,filesort有两种算法:mysql就要启动双路排序和单路排序
(2)无过滤不索引
(3)order by 非最左filesort
(4)顺序错 filesort
(5)方向反 filesort
答案:
(1)首先要对SQL进行分析检查必要的的查询字段、过滤字段、排序字段是否按顺序创建好了索引;
(2)如果查询字段不再索引中,可能会产生回表操作导致filesort降低性能;
(3)一定要过滤字段不然不能使用索引;
(4)排序字段和索引顺序不一致会导致filesort降低性能;
(5)多个字段排序时如果方向不一致也会导致filesort,降低性能;
(6)使用explain观察查询类型和索引利用情况;
(7)尽可能减少不必要的filesort;
实验准备:向表中插入50w条数据
在做优化之前,要准备大量数据。接下来创建两张表,并往员工表里插入50W数据,部门表中插入1W条数据。
怎么快速插入50w条数据呢? 存储过程
怎么保证插入的数据不重复?函数
部门表:
-
id:自增长
-
deptName:随机字符串,允许重复
-
address:随机字符串,允许重复
-
CEO:1-50w之间的任意数字
员工表:
- id:自增长
- empno:可以使用随机数字,或者
从1开始的自增数字
,不允许重复 - name:随机生成,允许姓名重复
- age:区间随机数
- deptId:1-1w之间随机数
总结: 需要产生随机字符串和区间随机数的函数。
创建表
CREATE TABLE `dept` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`deptName` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
ceo INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
CREATE TABLE `emp` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`empno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`deptId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1;
创建函数
-- 查看mysql是否允许创建函数:
SHOW VARIABLES LIKE 'log_bin_trust_function_creators';
-- 命令开启:允许创建函数设置:(global-所有session都生效)
SET GLOBAL log_bin_trust_function_creators=1;
-- 随机产生字符串
DELIMITER $$
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END $$
-- 假如要删除
-- drop function rand_string;
-- 用于随机产生区间数字
DELIMITER $$
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num -from_num+1));
RETURN i;
END$$
-- 假如要删除
-- drop function rand_num;
创建存储过程
-- 插入员工数据
DELIMITER $$
CREATE PROCEDURE insert_emp(START INT, max_num INT)
BEGIN
DECLARE i INT DEFAULT 0;
#set autocommit =0 把autocommit设置成0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO emp (empno, NAME, age, deptid ) VALUES ((START+i) ,rand_string(6), rand_num(30,50), rand_num(1,10000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
-- 删除
-- DELIMITER ;
-- drop PROCEDURE insert_emp;
-- 插入部门数据
DELIMITER $$
CREATE PROCEDURE insert_dept(max_num INT)
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptname,address,ceo ) VALUES (rand_string(8),rand_string(10),rand_num(1,500000));
UNTIL i = max_num
END REPEAT;
COMMIT;
END$$
-- 删除
-- DELIMITER ;
-- drop PROCEDURE insert_dept;
调用存储过程
-- 执行存储过程,往dept表添加1万条数据
CALL insert_dept(10000);
-- 执行存储过程,往emp表添加50万条数据,编号从100000开始
CALL insert_emp(100000,500000);
批量删除表索引
-- 批量删除某个表上的所有索引
DELIMITER $$
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND seq_in_index=1 AND index_name <>'PRIMARY' ;
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index ",_index," on ",tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END$$
-- 执行批量删除:dbname 库名称, tablename 表名称
CALL proc_drop_index("dbname","tablename");
最左前缀法则示例
假设index(a,b,c)
Where语句 | 索引是否被使用 |
---|---|
where a = 3 | Y,使用到a |
where a = 3 and b = 5 | Y,使用到a,b |
where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N |
where a = 3 and c = 5 | 使用到a, 但是c不可以,b中间断了 |
where a = 3 and b > 4 and c = 5 | 使用到a和b, c不能用在范围之后,b断了 |
where a is null and b is not null | is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不一定能用上索引(8.0) |
where a <> 3 | 不能使用索引 |
where abs(a) =3 | 不能使用 索引 |
where a = 3 and b like ‘kk%’ and c = 4 | Y,使用到a,b,c |
where a = 3 and b like ‘%kk’ and c = 4 | Y,只用到a |
where a = 3 and b like ‘%kk%’ and c = 4 | Y,只用到a |
where a = 3 and b like ‘k%kk%’ and c = 4 | Y,使用到a,b,c |
一般性建议
Ø 对于单键索引,尽量选择过滤性更好的索引(例如:手机号,邮件,身份证)
Ø 在选择组合索引的时候,过滤性最好的字段在索引字段顺序中,位置越靠前越好。
Ø 选择组合索引时,尽量包含where中更多字段的索引
Ø 组合索引出现范围查询时,尽量把这个字段放在索引次序的最后面
Ø 尽量避免造成索引失效的情况
开始实验
删除索引: drop index idx_age_name_deptid on emp;
创建索引:CREATE INDEX idx_age_deptid_name ON emp (age,deptid,NAME);
1、无过滤不索引
EXPLAIN SELECT * FROM emp ORDER BY age,deptid;
EXPLAIN SELECT * FROM emp ORDER BY age,deptid LIMIT 10;
增加过滤条件,使用上索引了。
EXPLAIN SELECT age FROM emp where age>1000 ORDER BY age,deptid,name;
2、查询条件与筛选条件
const查询排序后回表操作,不会filesort
EXPLAIN SELECT * FROM emp WHERE age=45 ORDER BY age,deptid,NAME;
range查询,Using index condition 没有完全使用索引
EXPLAIN SELECT * FROM emp WHERE age>45 ORDER BY age,deptid,NAME;
range查询,Using where; Using index 完全使用索引
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age,deptid,NAME;
3、顺序错 filesort
正常情况
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age,deptid,NAME;
order by顺序变化会导致filesort
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY deptid,age,NAME;
4、order by非最左 filesort
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY deptid,NAME;
5、方向反 必排序
8.0为Backward index scan 倒序索引
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age desc,deptid desc,NAME desc
方向反 产生filesort
EXPLAIN SELECT age FROM emp WHERE age>45 ORDER BY age desc,deptid desc,NAME asc;
二、数据库索引的原理?创建索引的缺点是什么,什么情况索引失效?优化数据库的方法有哪些?
1、数据库索引的原理?
(1)以MySQL为例,默认引擎innoDB使用了b+树实现索引,在索引查找时,实现了log(n)的时间复杂度;
(2)聚簇索引记录了主键ID【完整数据】,非聚簇索引的索引树中记录数据【索引字段+主键】;
(3)在聚簇索引的叶子节点中记录了完整的值,非聚簇索引的叶子节点记录的是主键以及索引字段,如果需要完整值的话需要回表操作,即使用主键去聚簇索引中再次查找完整数据;
(4)索引的叶子节点以链表的形式存储,方便顺序查找和排序;
2、创建索引的缺点是什么?
(1)会占用空间;(2)更新时会级联更新索引;(3)高并发写影响性能;
3、什么情况索引失效?
(1)计算、函数导致索引失效;(2)LIKE以%开头索引失效;(3)不等于【!=】;(4)IS NOT NULL 可能失效【不同版本和数据决定】IS NULL 有效;(5)类型转换导致索引失效;(6)数据库优化器觉得不用索引更快的时候失效;
4、优化数据库的方法有哪些?
(1)硬件【主要在存储层优化】;(2)网络;(3)操作系统调优;(4)表结构设计优化;(5)sql优化;(6)减少函数使用;(7)索引优化;(8)大字段及全文检索优化;(9)连接池优化;(10)事务优化;(11)数据库集群化;(12)加入缓存;(13)冷热存储;(14)分库分表;
三、数据连接池的工作机制是什么?
答:
1. 预先创建好一些数据库连接,放到连接池中
2. 连接池中设置最小连接数和最大连接数
3. 最小连接不断开和数据库的连接
4. 超过最小链接的那些线程,在长时间不使用时会被回收,也就是断开和数据库的连接
5. 所有向数据库发送请求的业务必须通过连接池获得数据库连接
6. 当连接全部在使用中,可以使用队列等待
四、说一下MySQL中事务的实现原理
Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问
InnoDB是一个多版本的存储引擎。它保存有关已更改行的旧版本的信息,以支持并发和回滚等事务特性。这些信息存储在一个称为回滚段的数据结构中的系统表空间或undo表空间中。参见第14.6.3.4节“撤消表空间”。InnoDB使用回滚段中的信息来执行事务回滚所需的撤消操作。它还使用这些信息构建行的早期版本,以实现一致的读取
MVCC 的实现依赖于:隐藏字段、Read View、undo log
隐藏字段
- A 6-byte
DB_TRX_ID
用来标识最近一次对本行记录做修改 (insert 、update) 的事务的标识符 ,即最后一次修改本行记录的事务 id。 如果是 delete 操作, 在 InnoDB 存储引擎内部也属于一次 update 操作,即更新行中的一个特殊位 ,将行标识为己删除,并非真正删除。 - A 7-byte
DB_ROLL_PTR
回滚指针,指向该行的 undo log 。如果该行未被更新,则为空. - A 6-byte
DB_ROW_ID
如果没有设置主键且该表没有唯一非空索引时,InnoDB
会使用该 id 来生成聚簇索引.
Read View
不同的事务隔离级别中,当有事物在执行过程中修改了数据(更新版本号),在并发事务时需要判断一下版本链中的哪个版本是当前事务可见的。为此InnoDB有了ReadView的概念,使用ReadView来记录和隔离不同事务并发时此记录的哪些版本是对当前访问事物可见的。
undo log
除了用来回滚数据,还可以读取可见版本的数据。以此实现非锁定读
答:
1. mysql是由mvcc实现的事务控制
2. MVCC 的实现依赖于:隐藏字段、Read View、undo log
3. 在不同的事务隔离级别下通过设置readview内容,控制了哪些数据可见于不可见
五、JVM
1、JVM 内存模型中,为什么要区分新生代和年老代,对于新生代,为什么又要区分eden 区survial区?
个人理解答案:
1、主流垃圾回收器cms g1都使用了分代收集算法;
2、正常系统运行时会有海量临时对象产生,这些对象短期内大部分就会失效,对于这些对象我们把他们临时放到新生代;
3、新生代满了之后需要清理垃圾对象,我们会吧有效对象复制到老年代,以便后续继续使用;
4、为了更有效的区分哪些对象应该被复制到老年代,所以在新生代划分出了eden区和s0 s1区;
5、细化后的新生代内部被再次划分以便保证高速读写同时内存连续;
【1】新对象会被保存到eden区(开始是空的所以内存连续),eden区满了会把有效对象复制到s0(s0也是空的所以也是连续空间);
【2】清空eden区(再次写入时又是连续空间)
【3】s0 和 s1在命名上互换,原来的s1等待写入(空的)
【4】eden区再次满了,重复上面步骤
文心一言AI答案:
2、说一下你理解的JVM原理?
JVM 运行时数据区域包含堆、栈、本地方法栈、方法区
答案:jvm是Java虚拟机,我们的class文件运行在虚拟机上,通过虚拟机解决了跨平台的问题,jvm中有jmm来管理Java内存访问的方式,不同的jvm实现性能关注有差异,现在主流的实现是Hotspot,垃圾回收器是G1,jvm运行时内存中分为方法区,堆,栈,本地方法栈,执行代码时需要执行引擎。
3、请简述一下JVM 加载 class 文件的原理机制
关键点:1. 类加载器;2. 魔数;3. 元空间:负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
魔数
- Class文件开头的四个字节的无符号整数称为魔数(Magic Number)。
- 魔数是Class文件的标识。值是固定的,为0xCAFEBABE
- 如果一个Class文件的头四个字节不是0xCAFEBABE,虚拟机在进行文件校验的时候会报错。使用魔数而不是扩展名来识别Class文件,主要是基于安全方面的考虑,因为文件扩展名可以随意更改。
类加载器分为四种:前三种为虚拟机自带的加载器。
-
启动类加载器(Bootstrap)C++
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
也叫系统类加载器,负责加载classpath中指定的jar包及目录中class
-
扩展类加载器(Extension)Java
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
-
应用程序类加载器(AppClassLoader)Java
-
用户自定义加载器 Java.lang.ClassLoader的子类,用户可以定制类的加载方式
工作过程:
- 1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
- 2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
- 3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
- 4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载
- 5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
其实这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
好处:防止内存中出现多份同样的字节码(安全性角度)
比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个 Object对象。
六、选择题
1、典型的JDBC程序按(DBECFA)顺序编写( 排序)
A.释放资源
B.获得与数据库的物理连接
C.执行SQL命令
D.注册JDBC Driver
E.创建不同类型的Statement
F.如果有结果集,处理结果集
2、下列语句哪一个正确(b)
A.Java 程序经编译后会产生 machine code
B.Java 程序经编译后会产生 byte code
C.Java 程序经编译后会产生 DLL
D.以上都不正确
3、存在使i+1<i的数吗?存在
int i = Integer.MAX_VALUE;
System.out.println(i>i+1);true
4、Integer和int的区别?
1、Integer是int的包装类,int则是Java的一种基本的数据类型;
2、integer实际是对象的引用,当new一个integer时,实际上生成一个指针指向对象,而int则直接存储数值;
3、integer的默认值是null,而int的默认值是0;
5、jsp的八个隐含对象有哪些?
关键点:
- jsp本质上就是servlet
- 答不上来关系也不大
答:
1 pageContext
类型:PageContext
代表:当前页面的上下文
作用:可以获取到页面中的其他隐含对象,同时它还是一个域对象。
2 request
类型:HttpServletRequest
代表:请求
作用:可以获取用户发送的请求信息,它也是一个域对象。
3 session
类型:HttpSession
代表:当前会话
作用:可以作为域对象,用来共享数据。
4 application
类型:ServletContext
代表:代表整个WEB应用
作用:是JavaWeb中最大的域对象。
5 response
类型:HttpServletResponse
代表:响应
作用:向浏览器发送响应信息
6 out
类型:JspWriter
代表:输出流
作用:可以向页面输出内容
7 config
类型:ServletConfig
代表:当前JSP的配置信息
作用:可以获取到Servlet标签中初始化参数
8 page
类型:Object 在service方法中有如下代码 Object page = this;
代表:代表当前JSP的对象
9 exception
类型:Throwable
代表:异常信息
作用:获取页面中的异常
6、下面程序能正常运行吗?
- null 是可以被强转成任何类型的;2. 静态方法不需要实例对象
public class NULL {
public static void haha() {
System.out.println("haha");
}
public static void main(String[] args) {
((NULL)null).haha();
}
}
七、A 系统远程调用 B 系统的接口,A 系统在完成调用之后,需要更新A 系统本地表
问题1:常见的远程调用有几种?
1. 自定义协议实现C/S RPC调用(实现复杂,需解决的问题多)
2. UDP广播类型(常见用于即时通讯类,速度快,但需要二次确认)
3. 目前主流的基于http协议调用(Restful风格的api,springcloud微服务调用使用的就是http)
4. dubbo协议(阿里出品,长连接,二进制封装数据,性能较高)
5. soap协议(古老的webservice系列框架,基于xml实现数据封装,http协议传输)
6. 异步响应式(WebFlux+Spring Data Reactive)
7. 服务器推(不主动调用,常见系统通知类)
问题2:对于有这些外部衔接的方法需要注意哪些问题?请写出注意问题及伪代码?
1、写好接口文档,方便后续维护和团队协作
2、统一化的报文结构
3、标准化的服务状态码
status_code 200;//请求成功
4、统一化请求日志记录及异常记录
GlobalExceptionHandler...
logger.error("服务异常",exception);
5、当请求延迟过高,可以快速失败
6、重试机制
// 服务列表
serverList = {ser1,ser2,ser3}
retrycount = 0;
retrymax=10;
maxrequesttime = 200ms;
getxxoo(){
try{
// 尝试请求数据
calldata(serverList)
}catch (请求失败 500类异常 || 请求超时 calldata的网络请求方法中){
// 拦截器记录日志
serverList.remove(刚才请求的服务器)
// 重试次数过多
if (retrycount == retrymax)
return "当前访问不可用稍后再试"
retrycount ++;
递归调用本方法
}
}
7、事务问题,可回滚
getData(){
分布式事务开启
get1();
get2();
get3();
事务结束
要么全成功 要么全失败
修改数据时使用 tryModify() 临时保存数据或通过Seata类似的框架实现
}
8、数据一致性问题
//有并发修改数据时,如果需要保证一致性,需要使用分布式锁
//修改方法
modifyxx(){
// 尝试加锁
boolean haslock = tryLock(id)
if(haslock){
httppost(url,data)
releaseLock(id)
}
else{
// 提示用户一会再试试
// 尝试再次获取锁
}
}
八、在如下代码中,当调用insertA 方法的时候,是否能在当insertA 方法调用insertB的时候,如果insertB 插入b表的时候出现异常,能否保证 insertA中的a 表插入成功,如果不能,应该如何修改。
@Service
@Transactional
public class TestServie {
@Autowired
private JdbcTemplate jt;
public void insertA(){
jt.execute("insert into a(m,n)values(1,2)");
insertB();
}
public void insertB(){
jt.execute("insert into b(h,i)values(1,2)");
}
}
答案:不能,在类上使用了@Transactional注解,默认开启了全局读写事务,因此可以将@Transactional加在方法上即可。
九、数据库三范式是什么?
1、第一范式:单表(字段)拆分到不可拆分为止【有主键,具有原子性,字段不可分割】;
2、第二范式:每一行数据唯一性;
3、第三范式:单行数据中保留唯一主键,建立外键关联表;
答案:
1. 数据库三范式是设计数据库表的方法论,能够充分的利用关系型数据库把实体有机的关联起来
2. 降低耦合度节省存储空间
十、浅谈ReentrantLock的设计
- ReentrantLock 是在多线程竞争资源时使用的锁,他是一个独占锁、可重入锁,也是悲观锁
- ReentrantLock 支持公平锁,对公平和非公平锁有不同的实现逻辑
- ReentrantLock 使用 aqs(AbstractQueuedSynchronizer)来实现的获取锁的线程队列等待的过程
- 内部使用了原子操作类 cas 比较线程与对应的锁关系
- 内部支持newCondition来灵活的控制获取到锁的线程的阻塞与释放
十一、值传递和引用传递问题
关键点:1. 引用传递;2. 堆栈内存分析
public class demo01 {
public static void main(String args[]) {
StringBuffer a = new StringBuffer("A");
StringBuffer b = new StringBuffer("B");
operater(a,b);
System.out.println(a+" "+b);// AB B
}
public static void operater(StringBuffer x, StringBuffer y) {
// x 是一个列表对象,x.append(y) 实际上是在修改了 x 对象本身,而不是替换整个对象。因此,当您在 operater 方法中调用 x.append(y),x 列表会被修改为 ['A', 'B']。由于 a 和 x 引用同一个对象,它们指向同一个内存地址。所以当您在 operater 方法中修改了 x,也会影响到 main 方法中的 a
x.append(y);
// 将x赋值给了y,这意味着y也会引用"AB"。然而,需要注意的是,这个赋值操作只会在operater方法内部起作用,并不会影响到main方法中的b对象。
y = x;
System.out.println(x+" "+y);// AB AB
}
}
十二、多线程
1、多线程中 start()方法和 run()方法的区别是什么?
答:
1. start用来启动线程,改变的是线程的状态,由就绪进入执行
2. run方法中包含要执行的代码,在执行状态时运行里面的代码,方法返回值是void
2、创建线程有几种方式?
(1)使用Thread类创建线程
public class MyThread extends Thread {
public void run() {
System.out.println("使用thread方式创建线程");
}
}
// 创建并启动线程
MyThread thread = new MyThread();
thread.start();
(2)使用Runnable接口创建线程
public class MyRunnable implements Runnable {
public void run() {
System.out.println("使用Runnable方式创建线程");
}
}
// 创建并启动线程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
(3)使用Executor框架创建线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThread implements Runnable {
public void run() {
System.out.println("使用ExecutorService方式创建线程");
}
public static void main(String[] args) {
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 提交任务到线程池
executor.submit(new Mythread());
// 关闭线程池
executor.shutdown();
}
}
3、简单写一个会导致死锁的程序
- 获得锁后不释放,其他线程一直拿不到锁
- 解决:不要嵌套获得多个锁
static Object lockA = new Object();
static Object lockB = new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + " 尝试获取锁A");
synchronized (lockA){
System.out.println(Thread.currentThread() + " 获取到了锁A");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + " 尝试获取锁B");
synchronized (lockB){
System.out.println(Thread.currentThread() + " 获取到了锁B");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread() + " 尝试获取锁B");
synchronized (lockB){
System.out.println(Thread.currentThread() + " 获取到了锁B");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread() + " 尝试获取锁A");
synchronized (lockA){
System.out.println(Thread.currentThread() + " 获取到了锁A");
}
}
}
}).start();
}
4、多线程变量同步
- 共享变量在多线程读取的时候,会被拉倒线程本地,white 在执行时,一直使用的都是本地变量的值,所以后续更改,线程内一直访问不到最新变量,程序会卡死
- 给变量增加 volatile关键字,保证多线程之间 变量可见性
public class ThreadSafeCache {
private int result;
public int getResult() {
return result;
}
public synchronized void setResult(int result) {
this.result = result;
}
public static void main(String[] args) {
ThreadSafeCache threadSafeCache = new ThreadSafeCache();
for (int i = 0; i < 8; i++) {
new Thread(() -> {
while (threadSafeCache.getResult() < 100) {
}
System.out.println("我执行了");
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
threadSafeCache.setResult(200);
}
}
5、定义了一个类T,其中定义了一个synchronized 方法do(); A=newT();B = new T()。当一个线程进入A对象的 do 方法后,另一个线程是否可进入B对象的do 方法?互不影响
1. synchronize 普通方法,锁的是当前对象
2. synchronize 静态方法,锁的是class
3. 同步代码块,可以手动指定锁对象是啥
6、多线程有几种实现方法,都是什么?
- 实现Runnable接口 void返回值
- 实现Callable接口 可以指定返回值
/**
* 1. 创建Callable的实现类,并重写call()方法,该方法为线程执行体,并且该方法有返回值
*/
class MyCallableThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "执行了!");
return 200;
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 2. 创建Callable的实例,并用FutureTask类来包装Callable对象
// 3. 创建FutureTask对象,需要一个Callable类型的参数
FutureTask task = new FutureTask<Integer>(new MyCallableThread());
// 4. 创建多线程,由于FutureTask的本质是Runnable的实现类,所以第一个参数可以直接使用task
new Thread(task, "threadName").start();
//new Thread(task, "threadName2").start();
/*while (!task.isDone()) {
System.out.println("wait...");
}*/
System.out.println(task.get());
System.out.println(Thread.currentThread().getName() + " over!");
}
}
7、编写一个基于线程安全的懒加载单例模式
关键点:1. 多线程访问;2. 防止直接new对象;3. 使用锁;4. 内部类加载实现、cas实现等
public class Test03 {
private Test03(){}
private static Test03 instance = null;
public static Test03 getInstance(){
//有可能在多线程访问的时候 重复new了对象
if(instance == null){
// 有可能有多个线程 进入抢锁等待状态
synchronized (Test03.class){
if (instance == null){
instance = new Test03();
}else {
return instance;
}
}
}
return instance;
}
}
十三、对于高并发、高可用问题的常用处理方式
1、对于高并发
关键点:
- 寻找瓶颈
- 分治思想,业务拆分,流程拆分
- 扩容
- 限流
- 分流 ,cdn 缓存
- 流量清洗 waf
- 多级缓存设计 后端缓存,客户端缓存
- 日志溯源
- 资源隔离
- 弹性扩缩容
- 降级处理 前后端 兜底数据
2、对于高可用
- 容器化管理 HA
- keepalived
十四、哪一个List实现了最快插入?
arraylist :数组实现的,查询,修改比较快
linkedlist:链表实现,插入,删除比较快,直接在最后插入数据即可
vector:jdk1.0就有 线程安全,效率特别低
十五、同步有几种实现方法,都是什么?
非阻塞
1. volatile
2. cas及atomic系列实现
阻塞
1. wait()
2. sleep
3. synchronized
4. Lock 的实现
1. ReentrantLock
2. ReentrantReadWriteLock 等
3. condition.await()
5. CountDownLatch
6. CyclicBarrier
7. Semaphore
8. BlockingQueue
十六、SpringBoot
1、springboot是如何管理版本依赖的?
1、springboot底层使用Maven管理依赖,通过控制pom.xml父子关系来完成细节配置,在父pom中定义具体框架和版本号以及额外的信息;
2、提供了很多场景的spring-boot-starter的pom.xml文件,来标准化的引入依赖避免冲突;
2、什么是自动配置?
关键点:
1. 看springboot的源码
2. @SpringBootApplication 中包含的@EnableAutoConfiguration,@SpringBootConfiguration,@ComponentScan 注解
3. @EnableAutoConfiguration 开启自动配置功能 内部还包含 @AutoConfigurationPackage、@Import
1. @AutoConfigurationPackage 中 @Import(AutoConfigurationPackages.Registrar.class) 引入了 Registrar.class 完成批量注册,默认扫描主启动类的包@Bean或者@Compont等注解
2. @Import(AutoConfigurationImportSelector.class)
4. @ComponentScan 扫描被@Compent(@Service@Controller)注解的bean 加载到容器中
5. @SpringBootConfiguration 表示这是一个SpringBoot的配置类
答:
1. 所加载使用的jar包 如果是比较常见的,spring都提供了默认配置,也就是不像以前一样需要把框架的每一个属性都要配置一遍,主要目的是简化开发
2. springboot 通过在启动类上添加注解 @SpringBootApplication 完成自动配置
3. 内部完成了读取每个jar包下的`META-INF/spring.factories`和spring-boot-autoconfigure-2.6.7.jar中的默认配置
3、起步依赖?
指的是各种starter重点是pom.xml,其中包含了框架所需要的其他依赖以及默认配置文件,不需要我们手动配置了。
4、什么是Spring的依赖注入?有哪些方法进行依赖注入?
依赖注入是指将依赖的对象实例交给spring帮我们注入管理,从而释放对对象的管理权,比如可以统一替换接口的实现,更高效的开发程序。
1、set方法注入;2、构造方法注入;3、@Autowired【用于自动装配bean,它可以根据类型进行注入】和@Resource【用于获取指定名称的bean,它可以根据名称进行注入】