diff --git a/.gitignore b/.gitignore
index 37d6ef0..fa43602 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ _config.yml
.settings
.springBeans
.sts4-cache
+.vscode
### IntelliJ IDEA ###
.idea
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9a17302
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "qiniu.access_key": "5zQ0AuES-7MGr7y89zIyMIK7JsNUlpTLggi5JtGu",
+ "qiniu.bucket": "sihai",
+ "qiniu.domain": "image.ouyangsihai.cn",
+ "qiniu.enable": true,
+ "qiniu.secret_key": "Mq3GKXAmESb4QGcv8mY8-lNWt42G0AFHpTKgl5Yh",
+ "pasteImageToQiniu.access_key": "5zQ0AuES-7MGr7y89zIyMIK7JsNUlpTLggi5JtGu",
+ "pasteImageToQiniu.bucket": "sihai",
+ "pasteImageToQiniu.domain": "image.ouyangsihai.cn",
+ "pasteImageToQiniu.secret_key": "Mq3GKXAmESb4QGcv8mY8-lNWt42G0AFHpTKgl5Yh"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index d5d8684..1c7180e 100644
--- a/README.md
+++ b/README.md
@@ -1,168 +1,201 @@
**[JavaInterview](https://github.com/OUYANGSIHAI/JavaInterview)** 是本人在备战春招及这几年学习的知识沉淀,这里面有很多都是自己的原创文章,同时,也有很多是本在备战春招的过程中觉得对面试特别有帮助的文章,**[JavaInterview](https://github.com/OUYANGSIHAI/JavaInterview)** 不一定可以帮助你进入到 BAT 等大厂,但是,如果你认真研究,仔细思考,我相信你也可以跟我一样幸运的进入到大厂。
-本人经常在 CSDN 写博客,累计**原创博客 400+**,拥有**访问量160W**,目前是 **CSDN 博客专家**,春招目前拿到了大厂offer。
+本人经常在 CSDN 写博客,累计**原创博客 400+**,拥有**访问量251W+**,**CSDN 博客专家**,CSDN博客地址:[https://sihai.blog.csdn.net](https://sihai.blog.csdn.net),春招目前拿到了大厂offer。
如果觉得有帮助,给个 **star** 好不好,哈哈(目前还不是很完善,后面会一一补充)。
-**JavaInterview 最新地址**:https://github.com/OUYANGSIHAI/JavaInterview
-
**一起冲!!!**
-#### Java面试思维导图预警
+👉 如果你不知道该学习什么的话,请看 [Java 学习线路图是怎样的?](https://zhuanlan.zhihu.com/p/392712685) (原创不易,欢迎点赞),这是 2021 最新最完善的 Java 学习路线!
+
+👉 Java学习资源汇总(个人总结)
+
+- **Java基础到Java实战全套学习视频教程,包括多个企业级实战项目**
+
+- **面试算法资料,这是总结的算法资料,学完基本可以应付80%大厂**
+
+- **大厂面试资料,一年时间总结,覆盖Java所有技术点**
-这里再分享一些我总结的**Java面试思维导图**,我靠这些导图拿到了一线互联网公司的offer,先来瞧瞧。
+- **面试思维导图,手打总结**
-
-
+👉 **Java各种电子书:各种技术相关的电子书**
+👉 **Java面试思维导图(手打)**,我靠这些导图拿到了一线互联网公司的offer,关注公众号,回复:`思维导图`;
-**划重点**:想要`导图源文件`,更多`Java面试思维导图`,请关注我的公众号 **程序员的技术圈子**,`微信扫描下面二维码`,回复:**思维导图**,获取思维导图,绿色通道关注福利,等你拿。
-
+**划重点**:获取上面的资源,请关注我的公众号 `程序员的技术圈子`,**微信扫描下面二维码**,回复:`Java资料`,获取思维导图,绿色通道关注福利,等你拿。
-
-
@SpringBootApplication
+ public class Springboot0101QuickstartApplication {
+
+ public static void main(String[] args) {
+ ConfigurableApplicationContext ctx = SpringApplication.run(Springboot0101QuickstartApplication.class, args);
+ //获取bean对象
+ BookController bean = ctx.getBean(BookController.class);
+ System.out.println("bean======>" + bean);
+ }
+ }
+
+ SpringBoot的引导类是Boot工程的执行入口,运行main方法就可以启动项目
+
+ SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean
+
+### 1.4、内置Tomcat
+
+
+
+* Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty
+
+* 内置服务器
+
+ tomcat(默认) apache出品,粉丝多,应用面广,负载了若干较重的组件
+
+ jetty 更轻量级,负载性能远不及tomcat
+
+ undertow undertow,负载性能勉强跑赢tomcat
+
+## 2、Rest风格
+
+### 2.1、什么是Rest
+
+1. 什么是 rest :
+
+ REST(Representational State Transfer)表现形式状态转换
+
+ 传统风格资源描述形式
+ http://localhost/user/getById?id=1 (得到id为1的用户)
+ http://localhost/user/saveUser (保存用户)
+
+ REST风格描述形式
+ http://localhost/user/1 (得到id为1的用户)
+ http://localhost/user (保存用户)
+
+2. 优点:
+
+ 隐藏资源的访问行为, 无法通过地址得知对资源是何种操作
+ 书写简化
+
+3. 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
+
+ GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源
+
+ http://localhost/users 查询全部用户信息 GET (查询)
+ http://localhost/users/1 查询指定用户信息 GET (查询)
+ http://localhost/users 添加用户信息 POST (新增/保存)
+ http://localhost/users 修改用户信息 PUT (修改/更新)
+ http://localhost/users/1 删除用户信息 DELETE (删除)
+
+注意:
+
+上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
+描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如: users、
+
+1. 根据REST风格对资源进行访问称为**RESTful**
+
+### 2.2、Rest入门案例
+
+步骤:
+
+①设定http请求动作(动词)
+
+使用 @RequestMapping 注解的 method 属性声明请求的方式
+
+使用 @RequestBody 注解 获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。get 请求方式不适用。
+
+使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。
+
+@RequestMapping(value=“/users”,method=RequestMethod.POST)
+
+
+
+②:设定请求参数(路径变量)
+
+使用`@PathVariable` 用于绑定 url 中的占位符。例如:请求 url 中 /delete/`{id}`,这个`{id}`就是 url 占位符。
+
+
+@RequestMapping
+
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HhzZsWGO-1657811363433)(SpringBoot.assets/image-20220312164532116.png)]](https://img-blog.csdnimg.cn/7fb5465b71a346d4b2e8920493c5e36c.png)
+
+@PathVariable
+
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yaepxdng-1657811363434)(SpringBoot.assets/image-20220312164552778.png)]](https://img-blog.csdnimg.cn/a9c22e3367a7480ea219baaa21de868e.png)
+
+@RequestBody @RequestParam @PathVariable
+
+
+### 2.3、Restful快速开发
+
+使用 `@RestController` 注解开发 RESTful 风格
+
+
+使用 @GetMapping @PostMapping @PutMapping @DeleteMapping 简化 `@RequestMapping` 注解开发
+
+
+
+### 3、配置文档
+
+### 3.1、基础配置
+
+1. 修改配置
+ 修改服务器端口
+ server.port=80
+ 关闭运行日志图标(banner)
+ spring.main.banner-mode=off
+ 设置日志相关
+ logging.level.root=debug
+
+# 服务器端口配置
+server.port=80
+
+# 修改banner
+# spring.main.banner-mode=off
+# spring.banner.image.location=logo.png
+
+# 日志
+logging.level.root=info
+
+1. SpringBoot内置属性查询
+ https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
+ 官方文档中参考文档第一项:Application Propertie
+
+### 3.2、配置文件类型
+
+* 配置文件格式
+ 
+
+* SpringBoot提供了多种属性配置方式
+
+ application.properties
+
+ server.port=80
+
+ application.yml
+
+ server:
+ port: 81
+
+ application.yaml
+
+ server:
+ port: 82
+
+
+
+### 3.3、配置文件加载优先级
+
+* SpringBoot配置文件加载顺序
+ application.properties > application.yml > application.yaml
+* 常用配置文件种类
+ application.yml
+
+### 3.4、yaml数据格式
+
+yaml
+
+YAML(YAML Ain’t Markup Language),一种数据序列化格式
+
+优点:
+ 容易阅读
+ 容易与脚本语言交互
+ 以数据为核心,重数据轻格式
+
+YAML文件扩展名
+ .yml(主流)
+ .yaml
+
+yaml语法规则
+基本语法
+
+key: value -> value 前面一定要有空格
+大小写敏感
+属性层级关系使用多行描述,每行结尾使用冒号结束
+使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
+属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
+# 表示注释
+核心规则:数据前面要加空格与冒号隔开
server:
+ servlet:
+ context-path: /hello
+ port: 82
+### 数据类型
+
+* 字面值表示方式
+ 
+
+# 字面值表示方式
+
+boolean: TRUE #TRUE,true,True,FALSE,false , False 均可
+float: 3.14 #6.8523015e+5 # 支持科学计数法
+int: 123 #0b1010_0111_0100_1010_1110 # 支持二进制、八进制、十六进制
+# null: ~ # 使用 ~ 表示 null
+string: HelloWorld # 字符串可以直接书写
+string2: "Hello World" # 可以使用双引号包裹特殊字符
+date: 2018-02-17 # 日期必须使用 yyyy-MM-dd 格式
+datetime: 2018-02-17T15:02:31+08:00 # 时间和日期之间使用 T 连接,最后使用 + 代表时区
+
+* 数组表示方式:在属性名书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔
+
+
+
+subject:
+ - Java
+ - 前端
+ - 大数据
+
+enterprise:
+ name: zhangsan
+ age: 16
+
+subject2:
+ - Java
+ - 前端
+ - 大数据
+likes: [王者荣耀,刺激战场] # 数组书写缩略格式
+
+users: # 对象数组格式
+ - name: Tom
+ age: 4
+
+ - name: Jerry
+ age: 5
+users2: # 对象数组格式二
+ -
+ name: Tom
+ age: 4
+ -
+ name: Jerry
+ age: 5
+
+# 对象数组缩略格式
+users3: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ]
+### 3.5、读取yaml单一属性数据
+
+* 使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
+ 
+
+ @Value("${country}")
+ private String country1;
+
+ @Value("${user.age}")
+ private String age1;
+
+ @Value("${likes[1]}")
+ private String likes1;
+
+ @Value("${users[1].name}")
+ private String name1;
+
+ @GetMapping
+ public String getById() {
+ System.out.println("springboot is running2...");
+ System.out.println("country1=>" + country1);
+ System.out.println("age1=>" + age1);
+ System.out.println("likes1=>" + likes1);
+ System.out.println("name1=>" + name1);
+ return "springboot is running2...";
+ }
+### 3.6、yaml文件中的变量应用
+
+* 在配置文件中可以使用属性名引用方式引用属性
+ 
+ 
+
+* 属性值中如果出现转移字符,需要使用双引号包裹
+
+ lesson: "Spring\tboot\nlesson"
+
+### 3.7、读取yaml全部属性数据
+
+* 封装全部数据到Environment对象
+* 注意 要导这个 包
+* **import org.springframework.core.env.Environment**
+ 
+ 
+
+### 3.8、读取yaml应用类型属性数据
+
+* 自定义对象封装指定数据
+
+
+
+* 自定义对象封装指定数据的作用
+ 
+
+# 创建类,用于封装下面的数据
+# 由spring帮我们去加载数据到对象中,一定要告诉spring加载这组信息
+# 使用时候从spring中直接获取信息使用
+
+datasource:
+ driver: com.mysql.jdbc.Driver
+ url: jdbc:mysql://localhost/springboot_db
+ username: root
+ password: root666123
//1.定义数据模型封装yaml文件中对应的数据
+//2.定义为spring管控的bean
+@Component
+//3.指定加载的数据
+@ConfigurationProperties(prefix = "datasource")
+public class MyDataSource {
+
+ private String driver;
+ private String url;
+ private String username;
+ private String password;
+
+ //省略get/set/tostring 方法
+}
+
+使用自动装配封装指定数据
+
+ @Autowired
+ private MyDataSource myDataSource;
+
+输出查看
+
+System.out.println(myDataSource);
+## 4、SpringBoot整合JUnit
+
+### 4.1、整合JUnit
+
+* 添加Junit的起步依赖 Spring Initializr 创建时自带
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+* SpringBoot整合JUnit
+
+ @SpringBootTest
+ class Springboot07JunitApplicationTests {
+ @Autowired
+ private BookService bookService;
+ @Test
+ public void testSave(){
+ bookService.save();
+ }
+ }
+* @SpringBootTest
+ 名称:@SpringBootTest
+ 类型:测试类注解
+ 位置:测试类定义上方
+ 作用:设置JUnit加载的SpringBoot启动类
+ 范例:
+
+ @SpringBootTest
+ class Springboot05JUnitApplicationTests {}
+
+### 4.2、整合JUnit——classes属性
+
+
+
+@SpringBootTest(classes = Springboot04JunitApplication.class)
+//@ContextConfiguration(classes = Springboot04JunitApplication.class)
+class Springboot04JunitApplicationTests {
+ //1.注入你要测试的对象
+ @Autowired
+ private BookDao bookDao;
+
+ @Test
+ void contextLoads() {
+ //2.执行要测试的对象对应的方法
+ bookDao.save();
+ System.out.println("two...");
+ }
+}
+
+注意:
+
+* 如果测试类在SpringBoot启动类的包或子包中,可以省略启动类的设置,也就是省略classes的设定
+
+## 5、SpringBoot整合MyBatis、MyBatisPlus
+
+### 5.1、整合MyBatis
+
+①:创建新模块,选择Spring初始化,并配置模块相关基础信息
+
+②:选择当前模块需要使用的技术集(MyBatis、MySQL)
+
+③:设置数据源参数
+
+#DB Configuration:
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot_db
+ username: root
+ password: 123456
+
+④:创建user表
+在 springboot_db 数据库中创建 user 表
+
+-- ----------------------------
+-- Table structure for `user`
+-- ----------------------------
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `username` varchar(50) DEFAULT NULL,
+ `password` varchar(50) DEFAULT NULL,
+ `name` varchar(50) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Records of user
+-- ----------------------------
+INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');
+INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');
+
+⑤:创建实体Bean
+
+public class User {
+ // 主键
+ private Long id;
+ // 用户名
+ private String username;
+ // 密码
+ private String password;
+ // 姓名
+ private String name;
+
+ //此处省略getter,setter,toString方法 .. ..
+
+}
+
+⑥: 定义数据层接口与映射配置
+
+@Mapper
+public interface UserDao {
+
+ @Select("select * from user")
+ public List getAll();
+}
+
+⑦:测试类中注入dao接口,测试功能
+
+@SpringBootTest
+class Springboot05MybatisApplicationTests {
+
+ @Autowired
+ private UserDao userDao;
+
+ @Test
+ void contextLoads() {
+ List userList = userDao.getAll();
+ System.out.println(userList);
+ }
+
+}
+
+⑧:运行如下
+
+[User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]
+### 5.2、常见问题处理
+
+SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区
+
+jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
+
+或在MySQL数据库端配置时区解决此问题
+
+1.MySQL 8.X驱动强制要求设置时区
+
+修改url,添加serverTimezone设定
+修改MySQL数据库配置(略)
+
+2.驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
+
+### 5.3、整合MyBatisPlus
+
+①:手动添加SpringBoot整合MyBatis-Plus的坐标,可以通过mvnrepository获取
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.4.3
+
+
+注意事项: 由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version
+
+②:定义数据层接口与映射配置,继承BaseMapper
+
+@Mapper
+public interface UserDao extends BaseMapper {
+
+}
+
+③:其他同SpringBoot整合MyBatis
+(略)
+
+④:测试类中注入dao接口,测试功能
+
+@SpringBootTest
+class Springboot06MybatisPlusApplicationTests {
+
+ @Autowired
+ private UserDao userDao;
+
+ @Test
+ void contextLoads() {
+ List users = userDao.selectList(null);
+ System.out.println(users);
+ }
+
+}
+
+⑤: 运行如下:
+
+[User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]
+
+注意: 如果你的数据库表有前缀要在 application.yml 添加如下配制
+
+#设置Mp相关的配置
+mybatis-plus:
+ global-config:
+ db-config:
+ table-prefix: tbl_
+### 6、SpringBoot整合Druid
+
+①: 导入Druid对应的starter
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.6
+
#DB Configuration:
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
+ username: root
+ password: Lemon
+ type: com.alibaba.druid.pool.DruidDataSource
+
+②: 指定数据源类型 (这种方式只需导入一个 Druid 的坐标)
+
+spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
+ username: root
+ password: 123456
+
+或者 变更Druid的配置方式(推荐) 这种方式需要导入 Druid对应的starter
+
+## 7、SSMP
+
+### 7.1、数据配置
+
+1\. 案例实现方案分析
+ 实体类开发————使用Lombok快速制作实体类
+ Dao开发————整合MyBatisPlus,制作数据层测试类
+ Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
+ Controller开发————基于Restful开发,使用PostMan测试接口功能
+ Controller开发————前后端开发协议制作
+ 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
+ 列表、新增、修改、删除、分页、查询
+ 项目异常处理
+ 按条件查询————页面功能调整、Controller修正功能、Service修正功能
+2\. SSMP案例制作流程解析
+ 先开发基础CRUD功能,做一层测一层
+ 调通页面,确认异步提交成功后,制作所有功能
+ 添加分页功能与查询功能
DROP TABLE IF EXISTS `tbl_book`;
+CREATE TABLE `tbl_book` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `type` varchar(20) DEFAULT NULL,
+ `name` varchar(50) DEFAULT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Records of tbl_book
+-- ----------------------------
+INSERT INTO `tbl_book` VALUES ('1', '计算机理论', 'Spring实战第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
+INSERT INTO `tbl_book` VALUES ('2', '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,写Spring精华思想');
+INSERT INTO `tbl_book` VALUES ('3', '计算机理论', 'Spring 5设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
+INSERT INTO `tbl_book` VALUES ('4', '计算机理论', 'Spring MVC+ MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
+INSERT INTO `tbl_book` VALUES ('5', '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
+INSERT INTO `tbl_book` VALUES ('6', '计算机理论', 'Java核心技术卷|基础知识(原书第11版)', 'Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、 11全面更新');
+INSERT INTO `tbl_book` VALUES ('7', '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,面试知识点全覆盖');
+INSERT INTO `tbl_book` VALUES ('8', '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典殿堂级著作!赢得了全球程序员的广泛赞誉');
+INSERT INTO `tbl_book` VALUES ('9', '计算机理论', '零基础学Java (全彩版)', '零基础自学编程的入门]图书,由浅入深,详解Java语言的编程思想和核心技术');
+INSERT INTO `tbl_book` VALUES ('10', '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
+INSERT INTO `tbl_book` VALUES ('11', '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
+INSERT INTO `tbl_book` VALUES ('12', '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书, 10堂课轻松实现带货月入3W+');
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.4.3
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.6
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.4.3
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.6
+
+
+
server:
+ port: 80
+# druid 数据源配制
+spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC
+ username: root
+ password: Lemon
+
+# mybatis-plus
+mybatis-plus:
+ global-config:
+ db-config:
+ table-prefix: tbl_
+ id-type: auto # 主键策略
+
+Book.class
+
+@Data
+public class Book {
+ private Integer id;
+ private String type;
+ private String name;
+ private String description;
+}
+
+BookDao.class
+
+@Mapper
+public interface BookDao extends BaseMapper {
+
+ /**
+ * 查询一个
+ * 这是 Mybatis 开发
+ * @param id
+ * @return
+ */
+ @Select("select * from tbl_book where id = #{id}")
+ Book getById(Integer id);
+}
+
+测试类
+
+@SpringBootTest
+public class BookDaoTestCase {
+
+ @Autowired
+ private BookDao bookDao;
+
+ @Test
+ void testGetById() {
+ System.out.println(bookDao.getById(1));
+ System.out.println(bookDao.selectById(1));
+ }
+
+ @Test
+ void testSave() {
+ Book book = new Book();
+ book.setType("测试数据123");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookDao.insert(book);
+ }
+
+ @Test
+ void testUpdate() {
+ Book book = new Book();
+ book.setId(13);
+ book.setType("测试数据asfd");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookDao.updateById(book);
+ }
+
+ @Test
+ void testDelete() {
+ bookDao.deleteById(13);
+ }
+
+ @Test
+ void testGetAll() {
+ System.out.println(bookDao.selectList(null));
+ }
+
+ @Test
+ void testGetPage() {
+ }
+
+ @Test
+ void testGetBy() {
+ }
+}
+
+开启MybatisPlus运行日志
+
+# mybatis-plus
+mybatis-plus:
+ global-config:
+ db-config:
+ table-prefix: tbl_
+ id-type: auto # 主键策略
+ configuration:
+ # 开启MyBatisPlus的日志
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+### 7.2、分页
+
+分页操作需要设定分页对象IPage
+
+@Test
+void testGetPage() {
+ IPage page = new Page(1, 5);
+ bookDao.selectPage(page, null);
+}
+
+* IPage对象中封装了分页操作中的所有数据
+
+ 数据
+
+ 当前页码值
+
+ 每页数据总量
+
+ 最大页码值
+
+ 数据总量
+
+* 分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能
+
+使用MyBatisPlus拦截器实现
+
+@Configuration
+public class MybatisPlusConfig {
+
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ //1\. 定义 Mp 拦截器
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ //2\. 添加具体的拦截器 分页拦截器
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+ return interceptor;
+ }
+}
+
+测试
+
+@Test
+void testGetPage() {
+ IPage page = new Page(1, 5);
+ bookDao.selectPage(page, null);
+ System.out.println(page.getCurrent());
+ System.out.println(page.getSize());
+ System.out.println(page.getPages());
+ System.out.println(page.getTotal());
+ System.out.println(page.getRecords());
+}
+### 7.3、数据层标准开发
+
+* 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
+
+@Test
+void testGetBy2() {
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ lambdaQueryWrapper.like(Book::getName, "Spring");
+ bookDao.selectList(lambdaQueryWrapper);
+}
@Test
+void testGetBy() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.like("name", "Spring");
+ bookDao.selectList(queryWrapper);
+}
+
+* 支持动态拼写查询条件
+
+@Test
+void testGetBy2() {
+ String name = "1";
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ //if (name != null) lambdaQueryWrapper.like(Book::getName,name);
+ lambdaQueryWrapper.like(Strings.isNotEmpty(name), Book::getName, name);
+ bookDao.selectList(lambdaQueryWrapper);
+}
+### 7.4、业务层标准开发(基础CRUD)
+
+* Service层接口定义与数据层接口定义具有较大区别,不要混用
+ selectByUserNameAndPassword(String username,String password); 数据层接口
+ login(String username,String password); Service层接口
+
+* 接口定义
+
+ public interface BookService {
+
+ Boolean save(Book book);
+
+ Boolean update(Book book);
+
+ Boolean delete(Integer id);
+
+ Book getById(Integer id);
+
+ List getAll();
+
+ IPage getPage(int currentPage,int pageSize);
+ }
+* 实现类定义
+
+@Service
+public class BookServiceImpl implements BookService {
+
+ @Autowired
+ private BookDao bookDao;
+
+ @Override
+ public Boolean save(Book book) {
+ return bookDao.insert(book) > 0;
+ }
+
+ @Override
+ public Boolean update(Book book) {
+ return bookDao.updateById(book) > 0;
+ }
+
+ @Override
+ public Boolean delete(Integer id) {
+ return bookDao.deleteById(id) > 0;
+ }
+
+ @Override
+ public Book getById(Integer id) {
+ return bookDao.selectById(id);
+ }
+
+ @Override
+ public List getAll() {
+ return bookDao.selectList(null);
+ }
+
+ @Override
+ public IPage getPage(int currentPage, int pageSize) {
+ IPage page = new Page(currentPage, pageSize);
+ bookDao.selectPage(page, null);
+ return page;
+ }
+}
+
+* 测试类定义
+
+@SpringBootTest
+public class BookServiceTestCase {
+
+ @Autowired
+ private BookService bookService;
+
+ @Test
+ void testGetById() {
+ System.out.println(bookService.getById(4));
+ }
+
+ @Test
+ void testSave() {
+ Book book = new Book();
+ book.setType("测试数据123");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.save(book);
+ }
+
+ @Test
+ void testUpdate() {
+ Book book = new Book();
+ book.setId(14);
+ book.setType("测试数据asfd");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.update(book);
+ }
+
+ @Test
+ void testDelete() {
+ bookService.delete(14);
+ }
+
+ @Test
+ void testGetAll() {
+ System.out.println(bookService.getAll());
+ }
+
+ @Test
+ void testGetPage() {
+ IPage page = bookService.getPage(2, 5);
+ System.out.println(page.getCurrent());
+ System.out.println(page.getSize());
+ System.out.println(page.getPages());
+ System.out.println(page.getTotal());
+ System.out.println(page.getRecords());
+ }
+}
+### 7.5、业务层快速开发(基于MyBatisPlus构建)
+
+* 快速开发方案
+
+ 使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现类(ServiceImplpublic interface IBookService extends IService {
+}
+
+* 接口追加功能
+
+public interface IBookService extends IService {
+
+ // 追加的操作与原始操作通过名称区分,功能类似
+ Boolean delete(Integer id);
+
+ Boolean insert(Book book);
+
+ Boolean modify(Book book);
+
+ Book get(Integer id);
+}
+
+
+
+* 实现类定义
+
+@Service
+public class BookServiceImpl extends ServiceImpl implements IBookService {
+}
+
+* 实现类追加功能
+
+@Service
+public class BookServiceImpl extends ServiceImpl implements IBookService {
+
+ @Autowired
+ private BookDao bookDao;
+
+ public Boolean insert(Book book) {
+ return bookDao.insert(book) > 0;
+ }
+
+ public Boolean modify(Book book) {
+ return bookDao.updateById(book) > 0;
+ }
+
+ public Boolean delete(Integer id) {
+ return bookDao.deleteById(id) > 0;
+ }
+
+ public Book get(Integer id) {
+ return bookDao.selectById(id);
+ }
+}
+
+* 测试类定义
+
+@SpringBootTest
+public class BookServiceTest {
+
+ @Autowired
+ private IBookService bookService;
+
+ @Test
+ void testGetById() {
+ System.out.println(bookService.getById(4));
+ }
+
+ @Test
+ void testSave() {
+ Book book = new Book();
+ book.setType("测试数据123");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.save(book);
+ }
+
+ @Test
+ void testUpdate() {
+ Book book = new Book();
+ book.setId(14);
+ book.setType("===========");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.updateById(book);
+ }
+
+ @Test
+ void testDelete() {
+ bookService.removeById(14);
+ }
+
+ @Test
+ void testGetAll() {
+ System.out.println(bookService.list());
+ }
+
+ @Test
+ void testGetPage() {
+ IPage page = new Page<>(2, 5);
+ bookService.page(page);
+ System.out.println(page.getCurrent());
+ System.out.println(page.getSize());
+ System.out.println(page.getPages());
+ System.out.println(page.getTotal());
+ System.out.println(page.getRecords());
+ }
+}
+### 7.7、表现层标准开发
+
+* 基于Restful进行表现层接口开发
+* 使用Postman测试表现层接口功能
+
+@RestController
+@RequestMapping("/books")
+public class BookController {
+
+ @Autowired
+ private IBookService bookService;
+
+ @GetMapping
+ public List getAll() {
+ return bookService.list();
+ }
+
+ @PostMapping
+ public Boolean save(@RequestBody Book book) {
+ return bookService.save(book);
+ }
+
+ @PutMapping
+ public Boolean update(@RequestBody Book book) {
+ return bookService.modify(book);
+ }
+
+ @DeleteMapping("{id}")
+ public Boolean delete(@PathVariable Integer id) {
+ return bookService.delete(id);
+ }
+
+ @GetMapping("{id}")
+ public Book getById(@PathVariable Integer id) {
+ return bookService.getById(id);
+ }
+
+ @GetMapping("{currentPage}/{pageSize}")
+ public IPage getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
+ return bookService.getPage(currentPage, pageSize);
+ }
+
+}
+
+添加 分页的业务层方法
+
+IBookService
+
+ IPage getPage(int currentPage,int pageSize);
+
+BookServiceImpl
+
+@Override
+public IPage getPage(int currentPage, int pageSize) {
+
+ IPage page = new Page(currentPage, pageSize);
+ bookDao.selectPage(page, null);
+
+ return page;
+}
+
+
+
+
+
+### 7.8、表现层数据一致性处理(R对象)
+
+统一格式
+
+
+
+
+* 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为**前后端数据协议**
+
+@Data
+public class R {
+ private Boolean flag;
+ private Object data;
+
+ public R() {
+ }
+
+ /**
+ * 不返回数据的构造方法
+ *
+ * @param flag
+ */
+ public R(Boolean flag) {
+ this.flag = flag;
+ }
+
+ /**
+ * 返回数据的构造方法
+ *
+ * @param flag
+ * @param data
+ */
+ public R(Boolean flag, Object data) {
+ this.flag = flag;
+ this.data = data;
+ }
+}
+
+* 表现层接口统一返回值类型结果
+
+@RestController
+@RequestMapping("/books")
+public class BookController {
+
+ @Autowired
+ private IBookService bookService;
+
+ @GetMapping
+ public R getAll() {
+ return new R(true, bookService.list());
+ }
+
+ @PostMapping
+ public R save(@RequestBody Book book) {
+ return new R(bookService.save(book));
+
+ }
+
+ @PutMapping
+ public R update(@RequestBody Book book) {
+ return new R(bookService.modify(book));
+ }
+
+ @DeleteMapping("{id}")
+ public R delete(@PathVariable Integer id) {
+ return new R(bookService.delete(id));
+ }
+
+ @GetMapping("{id}")
+ public R getById(@PathVariable Integer id) {
+ return new R(true, bookService.getById(id));
+ }
+
+ @GetMapping("{currentPage}/{pageSize}")
+ public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
+ return new R(true, bookService.getPage(currentPage, pageSize));
+ }
+
+}
+
+
+
+**前端部分省略**
+
+## 8、Springboot工程打包与运行
+
+### 8.1、程序为什么要打包
+
+将程序部署在独立的服务器上
+
+
+### 8.2、SpringBoot项目快速启动(Windows版)
+
+步骤
+
+①:对SpringBoot项目打包(执行[Maven](https://so.csdn.net/so/search?q=Maven&spm=1001.2101.3001.7020)构建指令package)
+执行 package 打包命令之前 先执行 **mvn clean** 删除 target 目录及内容
+
+mvn package
+
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eg4DFXqd-1657811363451)(SpringBoot.assets/image-20220317212408717.png)]](https://img-blog.csdnimg.cn/a19f1b3a110349cd9e16dbbd229ded5e.png)
+
+打包完成 生成对应的 jar 文件
+
+
+可能出现的问题: IDEA下 执行 Maven 命令控制台中文乱码
+Ctr+Alt+S 打开设置,在Build,Execution ,Deployment找到Build Tools下Maven项下的Runner ,在VM Options 添加
+-Dfile.encoding=GB2312 ,点击OK。
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5rHQGqK-1657811363452)(SpringBoot.assets/image-20220317212514627.png)]](https://img-blog.csdnimg.cn/74c0cddee08a4766bf1390fb3f7fac3f.png)
+
+②:运行项目(执行启动指令) java -jar <打包文件名>
+
+java –jar springboot.jar
+
+注意事项:
+jar支持命令行启动需要依赖maven插件支持,请确认打包时是否具有SpringBoot对应的maven插件
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+地址栏输入 cmd 回车
+
+
+执行 java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar
+
+
+打包优化:跳过 test 生命周期
+
+
+
+### 8.3、打包插件
+
+如果没有配制spring boot 打包插件可能遇到下面的问题:
+
+
+使用SpringBoot提供的maven插件可以将工程打包成可执行jar包
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+可执行jar包目录
+
+
+ar包描述文件(MANIFEST.MF)
+
+普通工程
+
+Manifest-Version: 1.0
+Implementation-Title: springboot_08_ssmp
+Implementation-Version: 0.0.1-SNAPSHOT
+Build-Jdk-Spec: 1.8
+Created-By: Maven Jar Plugin 3.2.0
+
+基于spring-boot-maven-plugin打包的工程
+
+Manifest-Version: 1.0
+Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
+Implementation-Title: springboot_08_ssmp
+Implementation-Version: 0.0.1-SNAPSHOT
+Spring-Boot-Layers-Index: BOOT-INF/layers.idx
+Start-Class: com.example.SSMPApplication 启动类
+Spring-Boot-Classes: BOOT-INF/classes/
+Spring-Boot-Lib: BOOT-INF/lib/
+Build-Jdk-Spec: 1.8
+Spring-Boot-Version: 2.5.6
+Created-By: Maven Jar Plugin 3.2.0
+Main-Class: org.springframework.boot.loader.JarLauncher jar启动器
+
+命令行启动常见问题及解决方案
+
+* Windonws端口被占用
+
+# 查询端口
+netstat -ano
+# 查询指定端口
+netstat -ano |findstr "端口号"
+# 根据进程PID查询进程名称
+tasklist |findstr "进程PID号"
+# 根据PID杀死任务
+taskkill /F /PID "进程PID号"
+# 根据进程名称杀死任务
+taskkill -f -t -im "进程名称"
+### 8.4、Boot工程快速启动
+
+
+
+* 基于Linux(CenterOS7)
+
+* 安装JDK,且版本不低于打包时使用的JDK版本
+
+ * 可以使用 yum 安装
+* 安装 MySQL
+
+ * 可以参考: https://blog.csdn.net/qq_42324086/article/details/120579197
+* 安装包保存在/usr/local/自定义目录中或$HOME下
+
+* 其他操作参照Windows版进行
+
+**启动成功无法访问**
+
+添加 80 端口
+
+* 添加 端口
+
+firewall-cmd --zone=public --permanent --add-port=80/tcp
+
+重启
+
+systemctl restart firewalld
+
+后台启动命令
+
+nohup java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar > server.log 2>&1 &
+
+停止服务
+
+* ps -ef | grep “java -jar”
+* kill -9 PID
+* cat server.log (查看日志)
+
+[root@cjbCentos01 app]# ps -ef | grep "java -jar"
+UID PID PPID C STIME TTY TIME CMD
+root 6848 6021 7 14:45 pts/2 00:00:19 java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar
+root 6919 6021 0 14:49 pts/2 00:00:00 grep --color=auto java -jar
+[root@cjbCentos01 app]# kill -9 6848
+[root@cjbCentos01 app]# ps -ef | grep "java -jar"
+root 7016 6021 0 14:52 pts/2 00:00:00 grep --color=auto java -jar
+[1]+ 已杀死 nohup java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar > server.log 2>&1
+[root@cjbCentos01 app]#
+### 8.6、临时属性
+
+#### 8.6.1、临时属性
+
+
+
+* 带属性数启动SpringBoot
+
+java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar --server.port=8080
+
+* 携带多个属性启动SpringBoot,属性间使用空格分隔
+
+属性加载优先顺序
+
+1. 参看 https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config
+ 
+
+#### 8.6.2、开发环境
+
+
+
+* 带属性启动SpringBoot程序,为程序添加运行属性
+ 
+ 
+
+在启动类中 main 可以通过 System.out.println(Arrays.toString(args)); 查看配制的属性
+
+通过编程形式带参数启动SpringBoot程序,为程序添加运行参数
+
+public static void main(String[] args) {
+ String[] arg = new String[1];
+ arg[0] = "--server.port=8080";
+ SpringApplication.run(SSMPApplication.class, arg);
+}
+
+不携带参数启动SpringBoot程序
+
+public static void main(String[] args) {
+ //可以在启动boot程序时断开读取外部临时配置对应的入口,也就是去掉读取外部参数的形参
+ SpringApplication.run(SSMPApplication.class);
+}
+### 8.7、配置环境
+
+
+
+#### 8.7.1、配置文件分类
+
+* SpringBoot中4级配置文件
+
+ * 1级:file :config/application.yml 【最高】
+
+ * 2级:file :application.yml
+
+ * 3级:classpath:config/application.yml
+
+ * 4级:classpath:application.yml 【最低】
+
+* 作用:
+
+ * 1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控
+
+ * 3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控
+
+#### 8.7.2、自定义配置文件
+
+
+
+通过启动参数加载配置文件(无需书写配置文件扩展名) --spring.config.name=eban
+
+
+properties与yml文件格式均支持
+
+* 通过启动参数加载指定文件路径下的配置文件 --spring.config.location=classpath:/ebank.yml
+ 
+
+properties与yml文件格式均支持
+
+* 通过启动参数加载指定文件路径下的配置文件时可以加载多个配置,后面的会覆盖前面的
+
+--spring.config.location=classpath:/ebank.yml,classpath:/ebank-server.yml
+
+
+
+注意事项:
+多配置文件常用于将配置进行分类,进行独立管理,或将可选配置单独制作便于上线更新维护
+
+自定义配置文件——重要说明
+
+* 单服务器项目:使用自定义配置文件需求较低
+
+* 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
+
+* 基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息
+
+#### 8.7.3、多环境开发(yaml)
+
+
+
+
+
+#应用环境
+#公共配制
+spring:
+ profiles:
+ active: dev
+
+#设置环境
+#开发环境
+---
+spring:
+ config:
+ activate:
+ on-profile: dev
+server:
+ port: 81
+
+#生产环境
+---
+spring:
+ profiles: pro
+server:
+ port: 80
+
+#测试环境
+---
+spring:
+ profiles: test
+server:
+ port: 82
+#### 8.7.4、多环境开发文件(yaml)
+
+
+
+多环境开发(YAML版)多配置文件格式
+主启动配置文件application.yml
+
+#应用环境
+#公共配制
+spring:
+ profiles:
+ active: test
+
+环境分类配置文件application-pro.yml
+
+server:
+ port: 81
+
+环境分类配置文件application-dev.yml
+
+server:
+ port: 82
+
+环境分类配置文件application-test.yml
+
+server:
+ port: 83
+#### 8.7.5、多环境分组管理
+
+* 根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下
+
+ * application-devDB.yml
+
+ * application-devRedis.yml
+
+ * application-devMVC.yml
+
+* 使用include属性在激活指定环境的情况下,同时对多个环境进行加载使其生效,多个环境间使用逗号分隔
+
+ spring:
+ profiles:
+ active: dev
+ include: devDB,devMVC
+
+ 注意事项:
+ **当主环境dev与其他环境有相同属性时,主环境属性生效;其他环境中有相同属性时,最后加载的环境属性生效**
+
+ The following profiles are active: devDB,devMVC,dev
+* 从Spring2.4版开始使用group属性替代include属性,降低了配置书写量
+
+* 使用**group**属性定义多种主环境与子环境的包含关系
+
+spring:
+ profiles:
+ active: dev
+ group:
+ "dev": devDB,devMVC
+ "pro": proDB,proMVC
+ "test": testDB,testRedis,testMVC
+
+注意事项:
+**使用group属性,会覆盖 主环境dev (active) 的内容,最后加载的环境属性生效**
+
+The following profiles are active: dev,devDB,devMVC
+#### 8.7.6、多环境开发控制
+
+
+Maven与SpringBoot多环境兼容
+①:Maven中设置多环境属性
+
+
+
+
+ dev_env
+
+ dev
+
+
+ true
+
+
+
+ pro_env
+
+ pro
+
+
+
+
+ test_env
+
+ test
+
+
+
+
+②:SpringBoot中引用Maven属性
+
+spring:
+ profiles:
+ active: @profile.active@
+ group:
+ "dev": devDB,devMVC
+ "pro": proDB,proMVC
+
+
+
+③:执行Maven打包指令,并在生成的boot打包文件.jar文件中查看对应信息
+问题:**修改pom.xml 文件后,启动没有生效 手动 compile 即可**
+
+
+或者 设置 IDEA进行自动编译
+
+
+## 9、日志
+
+### 9.1、日志基础操作
+
+日志(log)作用
+
+1. 编程期调试代码
+2. 运营期记录信息
+ * 记录日常运营重要信息(峰值流量、平均响应时长……)
+ * 记录应用报错信息(错误堆栈)
+ * 记录运维过程数据(扩容、宕机、报警……)
+
+代码中使用日志工具记录日志
+
+* 先引入 Lombok 工具类
+
+
+
+ org.projectlombok
+ lombok
+
+
+ ①:添加日志记录操作
+
+ @RestController
+ @RequestMapping("/books")
+ public class BookController {
+ private static final Logger log = LoggerFactory.getLogger(BookController.class);
+
+ @GetMapping
+ public String getById() {
+ System.out.println("springboot is running...");
+ log.debug("debug ...");
+ log.info("info ...");
+ log.warn("warn ...");
+ log.error("error ...");
+ return "springboot is running...";
+ }
+ }
+* 日志级别
+
+TRACE:运行堆栈信息,使用率低
+DEBUG:程序员调试代码使用
+INFO:记录运维过程数据
+WARN:记录运维过程报警数据
+ERROR:记录错误堆栈信息
+FATAL:灾难信息,合并计入ERRO
+
+②:设置日志输出级别
+
+# 开启 debug 模式,输出调试信息,常用于检查系统运行状况
+debug: true
+# 设置日志级别, root 表示根节点,即整体应用日志级别
+logging:
+ level:
+ root: debug
+
+③:设置日志组,控制指定包对应的日志输出级别,也可以直接控制指定包对应的日志输出级别
+
+logging:
+ # 设置分组
+ group:
+ # 自定义组名,设置当前组中所包含的包
+ ebank: com.example.controller,com.example.service,com.example.dao
+ iservice: com.alibaba
+ level:
+ root: info
+ # 设置某个包的日志级别
+# com.example.controller: debug
+ # 为对应组设置日志级别
+ ebank: warn
+### 9.2、快速创建日志对象
+
+* 使用lombok提供的注解@Slf4j简化开发,减少日志对象的声明操作
+
+ @Slf4j
+ //Rest模式
+ @RestController
+ @RequestMapping("/books")
+ public class BookController {
+
+ @GetMapping
+ public String getById(){
+ System.out.println("springboot is running...2");
+
+ log.debug("debug...");
+ log.info("info...");
+ log.warn("warn...");
+ log.error("error...");
+
+ return "springboot is running...2";
+ }
+
+ }
+
+### 9.3、日志输出格式控制
+
+
+
+* PID:进程ID,用于表明当前操作所处的进程,当多服务同时记录日志时,该值可用于协助程序员调试程序
+* 所属类/接口名:当前显示信息为SpringBoot重写后的信息,名称过长时,简化包名书写为首字母,甚至直接删除
+* 设置日志输出格式
+
+logging:
+ pattern:
+ console: "%d - %m%n"
+
+%d:日期
+%m:消息
+%n:换行
+
+
+logging:
+pattern:
+# console: "%d - %m%n"
+console: "%d %clr(%5p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"
+### 9.4、文件记录日志
+
+* 设置日志文件
+
+logging:
+ file:
+ name: server.log
+
+* 日志文件详细配置
+
+logging:
+ file:
+ name: server.log
+ logback:
+ rollingpolicy:
+ max-file-size: 4KB
+ file-name-pattern: server.%d{yyyy-MM-dd}.%i.log
+
+
+
+## 10、热部署
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
spring:
+ # 热部署范围配置
+ devtools:
+ restart:
+ # 设置不参与热部署的文件和文件夹(即修改后不重启)
+ exclude: static/**,public/**,config/application.yml
+ #是否可用
+ enabled: false
+
+如果配置文件比较多的时候找热部署对应配置比较麻烦,可以在`springboot`启动类的main方法中设置,此处设置的优先级将比配置文件高,一定会生效。
+
+System.setProperty("spring.devtools.restart.enabled", "false");
+## 11、属性绑定
+
+1. 先在要配置的类上面加@Component注解将该类交由spring容器管理;
+2. `@ConfigurationProperties(prefix="xxx")`,xxx跟application.yml配置文件中的属性对应;
+3. 如果多个配置类想统一管理也可以通过`@EnableConfigurationProperties({xxx.class, yyy.class})`的方式完成配置,不过该注解会与@Component配置发生冲突,二选一即可;
+4. 第三方类对象想通过配置进行属性注入,可以通过创建一个方法,在方法体上加@Bean和`@ConfigurationProperties(prefix="xxx")`注解,然后方法返回这个第三方对象的方式。
+5. 使用`@ConfigurationProperties(prefix="xxx")`注解后idea工具会报一个警告Spring Boot Configuration Annotation Processor not configured
+
+@ConfigurationProperties(prefix="xxx")
+@Data
+public class ServerConfig {
+ private String inAddress;
+ private int port;
+ private long timeout;
+}
+
+`@ConfigurationProperties`绑定属性支持属性名宽松绑定,又叫松散绑定。
+
+比如要将`ServerConfig.class`作为配置类,并通过配置文件`application.yml`绑定属性
+
+ServerConfig.class
serverConfig:
+ # ipAddress: 192.168.0.1 # 驼峰模式
+ # ipaddress: 192.168.0.1
+ # IPADDRESS: 192.168.0.1
+ ip-address: 192.168.0.1 # 主流配置方式,烤肉串模式
+ # ip_address: 192.168.0.1 # 下划线模式
+ # IP_ADDRESS: 192.168.0.1 # 常量模式
+ # ip_Add_rEss: 192.168.0.1
+ # ipaddress: 192.168.0.1
+ port: 8888
+ timeout: -1
+
+以ipAddress属性为例,上面的多种配置方式皆可生效,这就是松散绑定。而@Value不支持松散绑定,必须一一对应。
+
+`@ConfigurationProperties(prefix="serverconfig")`中的prefix的值为serverconfig或者server-config,如果是serverConfig就会报错,这与松散绑定的前缀命名规范有关:仅能使用纯小写字母、数字、中划线作为合法的字符
+
+## 12、常用计量单位应用
+
+//@Component
+@ConfigurationProperties(prefix = "server-config")
+@Data
+public class ServerConfig {
+ private String ipAddress;
+ private int port;
+ @DurationUnit(ChronoUnit.MINUTES)
+ private Duration timeout;
+
+ @DataSizeUnit(DataUnit.MEGABYTES)
+ private DataSize dataSize;
+}
+
+引入`Bean`属性校验框架的步骤:
+
+1. 在`pom.xml`中添加`JSR303`规范和`hibernate`校验框架的依赖:
+
+
+
+ javax.validation
+ validation-api
+
+
+
+ org.hibernate.validator
+ hibernate-validator
+
+
+1. 在要校验的类上加`@Validated`注解
+
+2. 设置具体的校验规则,如:`@Max(value=8888, message="最大值不能超过8888")`
+
+@ConfigurationProperties(prefix = "server-config")
+@Data
+// 2.开启对当前bean的属性注入校验
+@Validated
+public class ServerConfig {
+ private String ipAddress;
+ // 设置具体的规则
+ @Max(value = 8888, message = "最大值不能超过8888")
+ @Min(value = 1000, message = "最小值不能低于1000")
+ private int port;
+ @DurationUnit(ChronoUnit.MINUTES)
+ private Duration timeout;
+
+ @DataSizeUnit(DataUnit.MEGABYTES)
+ private DataSize dataSize;
+}
+
+进制转换中的一些问题:
+
+如`application.yml`文件中对数据库有如下配置:
+
+datasource:
+ driverClassName: com.mysql.cj.jdbc.Driver123
+ # 不加引号读取的时候默认解析为了8进制数,转成十进制就是87
+ # 所以想让这里正确识别,需要加上引号
+ # password: 0127
+ password: "0127"
+## 13、测试类
+
+### 13.1、加载专用属性
+
+@SpringBootTest注解中可以设置properties和args属性,这里的args属性的作用跟idea工具中自带的程序参数类似,只不过这里的配置是源码级别的,会随着源码的移动而跟随,而idea中的程序参数的配置会丢失。并且这里的args属性的配置的作用范围比较小,仅在当前测试类生效。
+
+application.yml
+
+test:
+ prop: testValue
// properties属性可以为当前测试用例添加临时的属性配置
+//@SpringBootTest(properties = {"test.prop=testValue1"})
+// args属性可以为当前测试用例添加临时的命令行参数
+//@SpringBootTest(args = {"--test.prop=testValue2"})
+// 优先级排序: args > properties > 配置文件
+@SpringBootTest(args = {"--test.prop=testValue2"}, properties = {"test.prop=testValue1"})
+class PropertiesAndArgsTest {
+ @Value("${test.prop}")
+ private String prop;
+ @Test
+ public void testProperties() {
+ System.out.println("prop = " + prop);
+ }
+}
+### 13.2、加载专用类
+
+某些测试类中需要用到第三方的类,而其他测试类则不需要用到,这里可以在类上加载`@Import({xxx.class, yyy.class})`
+
+@RestController
+@RequestMapping("/books")
+public class BookController {
+ /*@GetMapping("/{id}")
+ public String getById(@PathVariable int id) {
+ System.out.println("id = " + id);
+ return "getById...";
+ }*/
+
+ @GetMapping("/{id}")
+ public Book getById(@PathVariable int id) {
+ System.out.println("id = " + id);
+ Book book = new Book();
+ book.setId(5);
+ book.setName("springboot");
+ book.setType("springboot");
+ book.setDescription("springboot");
+ return book;
+ }
+}
+
+相应测试类
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+// 开启虚拟mvc调用
+@AutoConfigureMockMvc
+public class WebTest {
+ @Test
+ public void testRandomPort() {
+ }
+
+ @Test
+ public void testWeb(@Autowired MockMvc mvc) throws Exception {
+ // 创建虚拟请求,当前访问 /books
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/5");
+ mvc.perform(builder);
+ }
+
+ @Test
+ public void testStatus(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books1/6");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ StatusResultMatchers srm = MockMvcResultMatchers.status();
+ // 预计本次调用成功的状态码:200
+ ResultMatcher ok = srm.isOk();
+ // 添加预计值到本次调用过程中进行匹配
+ action.andExpect(ok);
+ }
+
+ @Test
+ public void testBody(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/6");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ ContentResultMatchers crm = MockMvcResultMatchers.content();
+ // 预计本次调用成功的状态码:200
+ ResultMatcher rm = crm.string("getById...");
+ // 添加预计值到本次调用过程中进行匹配
+ action.andExpect(rm);
+ }
+
+ @Test
+ public void testJson(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/7");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ ContentResultMatchers jsonMatcher = MockMvcResultMatchers.content();
+ ResultMatcher rm = jsonMatcher.json("{\"id\":5,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot1\"}");
+ action.andExpect(rm);
+ }
+
+ @Test
+ public void testContentType(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/7");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ HeaderResultMatchers hrm = MockMvcResultMatchers.header();
+ ResultMatcher rm = hrm.string("Content-Type", "application/json");
+ action.andExpect(rm);
+ }
+
+ @Test
+ // 完整测试
+ public void testGetById(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/8");
+ ResultActions action = mvc.perform(builder);
+
+ // 1、比较状态码
+ StatusResultMatchers statusResultMatchers = MockMvcResultMatchers.status();
+ ResultMatcher statusResultMatcher = statusResultMatchers.isOk();
+ action.andExpect(statusResultMatcher);
+
+ // 2、比较返回值类型
+ HeaderResultMatchers headerResultMatchers = MockMvcResultMatchers.header();
+ ResultMatcher headerResultMatcher = headerResultMatchers.string("Content-Type", "application/json");
+ action.andExpect(headerResultMatcher);
+
+ /// 3、比较json返回值
+ ContentResultMatchers contentResultMatchers = MockMvcResultMatchers.content();
+ ResultMatcher jsonResultMatcher = contentResultMatchers.json("{\"id\":5,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot\"}");
+ action.andExpect(jsonResultMatcher);
+ }
+}
+### 13.3、业务层测试事务回滚
+
+* 为测试用例添加事务,SpringBoot会对测试用例对应的事务提交操作进行回滚
+
+ @SpringBootTest
+ @Transactional public class DaoTest {
+ @Autowired
+ private BookService bookService; }
+* l如果想在测试用例中提交事务,可以通过@Rollback注解设置
+
+ @SpringBootTest
+ @Transactional
+ @Rollback(false)
+ public class DaoTest {
+ }
+
+### 13.4、测试用例数据设定
+
+* 测试用例数据通常采用随机值进行测试,使用SpringBoot提供的随机数为其赋值
+
+testcast:
+ book:
+ id: ${random.int} # 随机整数
+ id2: ${random.int(10)} # 10以内随机数
+ type: ${random.int(10,20)} # 10到20随机数
+ uuid: ${random.uuid} # 随机uuid
+ name: ${random.value} # 随机字符串,MD5字符串,32位
+ publishTime: ${random.long} # 随机整数(long范围)
u${random.int}表示随机整数
+
+u${random.int(10)}表示10以内的随机数
+
+u${random.int(10,20)}表示10到20的随机数
+
+u其中()可以是任意字符,例如[],!!均可
+## 14、数据层解决方案
+
+### 14.1、SQL
+
+现有数据层解决方案技术选型
+
+Druid + MyBatis-Plus + MySQL
+
+* 数据源:DruidDataSource
+* 持久化技术:MyBatis-Plus / MyBatis
+* 数据库:MySQL
+
+格式一:
+
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
+
+格式二:
+
+spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
+
+* SpringBoot提供了3种内嵌的数据源对象供开发者选择
+ * HikariCP
+ * Tomcat提供DataSource
+ * Commons DBCP
+
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
+
+* SpringBoot提供了3种内嵌的数据源对象供开发者选择
+
+ * HikariCP:默认内置数据源对象
+
+ * Tomcat提供DataSource:HikariCP不可用的情况下,且在web环境中,将使用tomcat服务器配置的数据源对象
+
+ * Commons DBCP:Hikari不可用,tomcat数据源也不可用,将使用dbcp数据源
+
+* 通用配置无法设置具体的数据源配置信息,仅提供基本的连接相关配置,如需配置,在下一级配置中设置具体设定
+
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db
+ username: root
+ password: root
+ hikari:
+ maximum-pool-size: 50
+
+* 内置持久化解决方案——JdbcTemplate
+
+@SpringBootTest
+class Springboot15SqlApplicationTests {
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+ @Test
+ void testJdbc(){
+ String sql = "select * from tbl_book where id = 1";
+ List query = jdbcTemplate.query(sql, new RowMapper() {
+ @Override
+ public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
+ Book temp = new Book();
+ temp.setId(rs.getInt("id"));
+ temp.setName(rs.getString("name"));
+ temp.setType(rs.getString("type"));
+ temp.setDescription(rs.getString("description"));
+ return temp;
+ }
+ });
+ System.out.println(query);
+ }
+}
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
spring:
+ jdbc:
+ template:
+ query-timeout: -1 # 查询超时时间
+ max-rows: 500 # 最大行数
+ fetch-size: -1 # 缓存行数
+
+* SpringBoot提供了3种内嵌数据库供开发者选择,提高开发测试效率
+
+ * H2
+ * HSQL
+ * Derby
+* 导入H2相关坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+ runtime
+
+* 设置当前项目为web工程,并配置H2管理控制台参数
+
+server:
+ port: 80
+spring:
+ h2:
+ console:
+ path: /h2
+ enabled: true
+
+访问用户名sa,默认密码123456
+
+操作数据库(创建表)
+
+create table tbl_book (id int,name varchar,type varchar,description varchar)
#设置访问数据源
+server:
+ port: 80
+spring:
+ datasource:
+ driver-class-name: org.h2.Driver
+ url: jdbc:h2:~/test
+ username: sa
+ password: 123456
+h2:
+ console:
+ path: /h2
+ enabled: true
+
+H2数据库控制台仅用于开发阶段,线上项目请务必关闭控制台功能
+
+server:
+ port: 80
+spring:
+ h2:
+ console:
+ path: /h2
+ enabled: false
+
+SpringBoot可以根据url地址自动识别数据库种类,在保障驱动类存在的情况下,可以省略配置
+
+server:
+ port: 80
+spring:
+ datasource:
+# driver-class-name: org.h2.Driver
+ url: jdbc:h2:~/test
+ username: sa
+ password: 123456
+ h2:
+ console:
+ path: /h2
+ enabled: true
+
+
+
+### 14.2、MongoDB
+
+MongoDB是一个开源、高性能、无模式的文档型数据库。NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库
+
+
+#### 14.2.1、MongoDB的使用
+
+* Windows版Mongo下载
+
+https://www.mongodb.com/try/download
+
+* Windows版Mongo安装
+
+解压缩后设置数据目录
+
+* Windows版Mongo启动
+
+服务端启动
+
+在bin目录下
+
+`mongod --dbpath=..\data\db`
+
+客户端启动
+
+`mongo --host=127.0.0.1 --port=27017`
+
+#### 14.2.2、MongoDB可视化客户端
+
+
+
+* 新增
+
+ `db.集合名称.insert/save/insertOne(文档)`
+
+* 修改
+
+ `db.集合名称.remove(条件)`
+
+* 删除
+
+ `db.集合名称.update(条件,{操作种类:{文档}})`
+ 
+ 
+
+#### 14.2.3、Springboot集成MongoDB
+
+导入MongoDB驱动
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+配置客户端
+
+spring:
+ data:
+ mongodb:
+ uri: mongodb://localhost/itheima
+
+客户端读写MongoDB
+
+@Test
+void testSave(@Autowired MongoTemplate mongoTemplate){
+ Book book = new Book();
+ book.setId(1);
+ book.setType("springboot");
+ book.setName("springboot");
+ book.setDescription("springboot");
+ mongoTemplate.save(book);
+}
+@Test
+void testFind(@Autowired MongoTemplate mongoTemplate){
+ List all = mongoTemplate.findAll(Book.class);
+ System.out.println(all);
+}
+### 14.3、ElasticSearch(ES)
+
+Elasticsearch是一个分布式全文搜索引擎
+
+
+
+
+
+#### 14.3.1、ES下载
+
+* Windows版ES下载
+
+[https://](https://www.elastic.co/cn/downloads/elasticsearch)[www.elastic.co/cn/downloads/elasticsearch](https://www.elastic.co/cn/downloads/elasticsearch)
+
+* Windows版ES安装与启动
+
+运行:`elasticsearch.bat`
+
+#### 14.3.2、ES索引、分词器
+
+* 创建/查询/删除索引
+
+put:`http://localhost:9200/books`
+
+get:`http://localhost:9200/books`
+
+delete:`http://localhost:9200/books`
+
+* IK分词器
+
+ 下载:https://github.com/medcl/elasticsearch-analysis-ik/releases
+
+* 创建索引并指定规则
+
+{
+ "mappings":{
+ "properties":{
+ "id":{
+ "type":"keyword"
+ },
+ "name":{
+ "type":"text", "analyzer":"ik_max_word", "copy_to":"all"
+ },
+ "type":{
+ "type":"keyword"
+ },
+ "description":{
+ "type":"text", "analyzer":"ik_max_word", "copy_to":"all"
+ },
+ "all":{
+ "type":"text", "analyzer":"ik_max_word"
+ }
+ }
+ }
+}
+#### 14.3.3、文档操作(增删改查)
+
+* 创建文档
+
+ post:`http://localhost:9200/books/_doc`(使用系统生成的id)
+
+ post:`http://localhost:9200/books/_create/1`(使用指定id)
+
+ post:`http://localhost:9200/books/_doc/1`(使用指定id,不存在创建,存在更新,版本递增)
+
+ {
+ "name":"springboot",
+ "type":"springboot",
+ "description":"springboot"
+ }
+* 查询文档
+
+ get:`http://localhost:9200/books/_doc/1`
+
+ get:`http://localhost:9200/books/_search`
+
+* 条件查询
+
+ get:`http://localhost:9200/books/_search?q=name:springboot`
+
+* 删除文档
+
+ delete:`http://localhost:9200/books/_doc/1`
+
+* 修改文档(全量修改)
+
+ put:`http://localhost:9200/books/_doc/1`
+
+ {
+ "name":"springboot",
+ "type":"springboot",
+ "description":"springboot"
+ }
+* 修改文档(部分修改)
+
+ post:`http://localhost:9200/books/_update/1`
+
+ {
+ "doc":{
+ "name":"springboot"
+ }
+ }
+
+#### 14.3.4、Springboot集成ES
+
+* 导入坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-data-elasticsearch
+
+* 配置
+
+ spring:
+ elasticsearch:
+ rest:
+ uris: http://localhost:9200
+* 客户端
+
+ @SpringBootTest
+ class Springboot18EsApplicationTests {
+ @Autowired
+ private ElasticsearchRestTemplate template;
+ }
+* SpringBoot平台并没有跟随ES的更新速度进行同步更新,ES提供了High Level Client操作ES
+
+ 导入坐标
+
+
+ org.elasticsearch.client
+ elasticsearch-rest-high-level-client
+
+
+ 无需配置
+
+* 客户端
+
+ @Test
+ void test() throws IOException {
+ HttpHost host = HttpHost.create("http://localhost:9200");
+ RestClientBuilder builder = RestClient.builder(host);
+ RestHighLevelClient client = new RestHighLevelClient(builder);
+ //客户端操作
+ CreateIndexRequest request = new CreateIndexRequest("books");
+ //获取操作索引的客户端对象,调用创建索引操作
+ client.indices().create(request, RequestOptions.DEFAULT);
+ //关闭客户端
+ client.close();
+ }
+* 客户端改进
+
+ @SpringBootTest
+ class Springboot18EsApplicationTests {
+ @Autowired
+ private BookDao bookDao;
+ @Autowired
+ RestHighLevelClient client;
+ @BeforeEach
+ void setUp() {
+ this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200")));
+ }
+ @AfterEach
+ void tearDown() throws IOException {
+ this.client.close();
+ }
+ @Test
+ void test() throws IOException {
+ //客户端操作
+ CreateIndexRequest request = new CreateIndexRequest("books");
+ //获取操作索引的客户端对象,调用创建索引操作
+ client.indices().create(request, RequestOptions.DEFAULT);
+ }
+ }
+
+#### 14.3.5、索引
+
+* 创建索引
+
+ //创建索引
+ @Test
+ void testCreateIndexByIK() throws IOException {
+ HttpHost host = HttpHost.create("http://localhost:9200");
+ RestClientBuilder builder = RestClient.builder(host);
+ RestHighLevelClient client = new RestHighLevelClient(builder);
+ //客户端操作
+ CreateIndexRequest request = new CreateIndexRequest("books");
+ //设置要执行操作
+ String json = "";
+ //设置请求参数,参数类型json数据
+ request.source(json,XContentType.JSON);
+ //获取操作索引的客户端对象,调用创建索引操作
+ client.indices().create(request, RequestOptions.DEFAULT);
+ //关闭客户端
+ client.close();
+ }
String json = "{\n" +
+ " \"mappings\":{\n" +
+ " \"properties\":{\n" +
+ " \"id\":{\n" +
+ " \"type\":\"keyword\"\n" +
+ " },\n" +
+ " \"name\":{\n" +
+ " \"type\":\"text\",\n" +
+ " \"analyzer\":\"ik_max_word\",\n" +
+ " \"copy_to\":\"all\"\n" +
+ " },\n" +
+ " \"type\":{\n" +
+ " \"type\":\"keyword\"\n" +
+ " },\n" +
+ " \"description\":{\n" +
+ " \"type\":\"text\",\n" +
+ " \"analyzer\":\"ik_max_word\",\n" +
+ " \"copy_to\":\"all\"\n" +
+ " },\n" +
+ " \"all\":{\n" +
+ " \"type\":\"text\",\n" +
+ " \"analyzer\":\"ik_max_word\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+* 添加文档
+
+ //添加文档
+ @Test
+ void testCreateDoc() throws IOException {
+ Book book = bookDao.selectById(1);
+ IndexRequest request = new IndexRequest("books").id(book.getId().toString());
+ String json = JSON.toJSONString(book);
+ request.source(json,XContentType.JSON);
+ client.index(request, RequestOptions.DEFAULT);
+ }
+* 批量添加文档
+
+ //批量添加文档
+ @Test
+ void testCreateDocAll() throws IOException {
+ List bookList = bookDao.selectList(null);
+ BulkRequest bulk = new BulkRequest();
+ for (Book book : bookList) {
+ IndexRequest request = new IndexRequest("books").id(book.getId().toString());
+ String json = JSON.toJSONString(book);
+ request.source(json,XContentType.JSON);
+ bulk.add(request);
+ }
+ client.bulk(bulk,RequestOptions.DEFAULT);
+ }
+* 按id查询文档
+
+ @Test
+ void testGet() throws IOException {
+ GetRequest request = new GetRequest("books","1");
+ GetResponse response = client.get(request, RequestOptions.DEFAULT);
+ String json = response.getSourceAsString();
+ System.out.println(json);
+ }
+* 按条件查询文档
+
+@Test
+void testSearch() throws IOException {
+ SearchRequest request = new SearchRequest("books");
+ SearchSourceBuilder builder = new SearchSourceBuilder();
+ builder.query(QueryBuilders.termQuery("all",“java"));
+ request.source(builder);
+ SearchResponse response = client.search(request, RequestOptions.DEFAULT);
+ SearchHits hits = response.getHits();
+ for (SearchHit hit : hits) {
+ String source = hit.getSourceAsString();
+ Book book = JSON.parseObject(source, Book.class);
+ System.out.println(book);
+ }
+}
+## 15、缓存
+
+### 15.1、缓存简介
+
+缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
+
+
+* 缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
+
+* 使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能
+
+* 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间
+ 
+
+* SpringBoot提供了缓存技术,方便缓存使用
+
+### 15.2、缓存使用
+
+* 启用缓存
+
+* 设置进入缓存的数据
+
+* 设置读取缓存的数据
+
+* 导入缓存技术对应的starter
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+* 启用缓存
+
+ @SpringBootApplication
+ @EnableCaching
+ public class Springboot19CacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot19CacheApplication.class, args);
+ }
+ }
+* 设置当前操作的结果数据进入缓存
+
+@Cacheable(value="cacheSpace",key="#id")
+public Book getById(Integer id) {
+ return bookDao.selectById(id);
+}
+### 15.3、其他缓存
+
+* SpringBoot提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理
+ * Generic
+ * JCache
+ * **Ehcache**
+ * Hazelcast
+ * Infinispan
+ * Couchbase
+ * **Redis**
+ * Caffeine
+ * Simple(默认)
+ * **memcached**
+ * jetcache(阿里)
+
+### 15.4、缓存使用案例——手机验证码
+
+* 需求
+
+ * 输入手机号获取验证码,组织文档以短信形式发送给用户(页面模拟)
+
+ * 输入手机号和验证码验证结果
+
+* 需求分析
+
+ * 提供controller,传入手机号,业务层通过手机号计算出独有的6位验证码数据,存入缓存后返回此数据
+
+ * 提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,返回比对结果
+
+#### 15.4.1、Cache
+
+* 开启缓存
+
+ @SpringBootApplication
+ @EnableCaching
+ public class Springboot19CacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot19CacheApplication.class, args);
+ }
+ }
+* 业务层接口
+
+ public interface SMSCodeService {
+ /**
+ * 传入手机号获取验证码,存入缓存
+ * @param tele
+ * @return
+ */
+ String sendCodeToSMS(String tele);
+
+ /**
+ * 传入手机号与验证码,校验匹配是否成功
+ * @param smsCode
+ * @return
+ */
+ boolean checkCode(SMSCode smsCode);
+ }
+* 业务层设置获取验证码操作,并存储缓存,手机号为key,验证码为value
+
+ @Autowired
+ private CodeUtils codeUtils;
+ @CachePut(value = "smsCode",key="#tele")
+ public String sendCodeToSMS(String tele) {
+ String code = codeUtils.generator(tele);
+ return code;
+ }
+* 业务层设置校验验证码操作,校验码通过缓存读取,返回校验结果
+
+ @Autowired
+ private CodeUtils codeUtils;
+ public boolean checkCode(SMSCode smsCode) {
+ //取出内存中的验证码与传递过来的验证码比对,如果相同,返回true
+ String code = smsCode.getCode();
+ String cacheCode = codeUtils.get(smsCode.getTele());
+ return code.equals(cacheCode);
+ }
@Component
+ public class CodeUtils {
+ @Cacheable(value = "smsCode",key="#tele")
+ public String get(String tele){
+ return null;
+ }
+ }
+
+#### 15.4.2、Ehcache
+
+* 加入Ehcache坐标(缓存供应商实现)
+
+
+ net.sf.ehcache
+ ehcache
+
+* 缓存设定为使用Ehcache
+
+ spring:
+ cache:
+ type: ehcache
+ ehcache:
+ config: ehcache.xml
+* 提供ehcache配置文件ehcache.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#### 15.4.3、Redis
+
+* 加入Redis坐标(缓存供应商实现)
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+* 配置Redis服务器,缓存设定为使用Redis
+
+ spring:
+ redis:
+ host: localhost
+ port: 6379
+ cache:
+ type: redis
+* 设置Redis相关配置
+
+ spring:
+ redis:
+ host: localhost
+ port: 6379
+ cache:
+ type: redis
+ redis:
+ use-key-prefix: true # 是否使用前缀名(系统定义前缀名)
+ key-prefix: sms_ # 追加自定义前缀名
+ time-to-live: 10s # 有效时长
+ cache-null-values: false # 是否允许存储空值
+
+#### 15.4.4、memcached
+
+* 下载memcached
+
+* 地址:https://www.runoob.com/memcached/window-install-memcached.html
+
+* 安装memcached
+
+ * 使用管理员身份运行cmd指令
+
+ * 安装
+
+ `memcached.exe -d install`
+
+* 运行
+
+ * 启动服务
+
+ `memcached.exe -d start`
+
+ * 定制服务
+
+ `memcached.exe -d stop`
+
+* memcached客户端选择
+
+ * Memcached Client for Java:最早期客户端,稳定可靠,用户群广
+ * SpyMemcached:效率更高
+ * Xmemcached:并发处理更好
+* SpringBoot未提供对memcached的整合,需要使用硬编码方式实现客户端初始化管理
+
+* 加入Xmemcached坐标(缓存供应商实现)
+
+
+ com.googlecode.xmemcached
+ xmemcached
+ 2.4.7
+
+* 配置memcached服务器必要属性
+
+ memcached:
+ # memcached服务器地址
+ servers: localhost:11211
+ # 连接池的数量
+ poolSize: 10
+ # 设置默认操作超时
+ opTimeout: 3000
+* 创建读取属性配置信息类,加载配置
+
+ @Component
+ @ConfigurationProperties(prefix = "memcached")
+ @Data
+ public class XMemcachedProperties {
+ private String servers;
+ private Integer poolSize;
+ private Long opTimeout;
+ }
+* 创建客户端配置类
+
+ @Configuration
+ public class XMemcachedConfig {
+ @Autowired
+ private XMemcachedProperties xMemcachedProperties;
+ @Bean
+ public MemcachedClient getMemcachedClinet() throws IOException {
+ MemcachedClientBuilder builder = new XMemcachedClientBuilder(xMemcachedProperties.getServers());
+ MemcachedClient memcachedClient = builder.build();
+ return memcachedClient;
+ }
+ }
+* 配置memcached属性
+
+ @Service
+ public class SMSCodeServiceMemcacheImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @Autowired
+ private MemcachedClient memcachedClient;
+ @Override
+ public String sendCodeToSMS(String tele) {
+ String code = this.codeUtils.generator(tele);
+ //将数据加入memcache
+ try {
+ memcachedClient.set(tele,0,code); // key,timeout,value
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return code;
+ }
+ }
@Service
+ public class SMSCodeServiceMemcacheImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @Autowired
+ private MemcachedClient memcachedClient;
+ @Override
+ public boolean checkCode(CodeMsg codeMsg) {
+ String value = null;
+ try {
+ value = memcachedClient.get(codeMsg.getTele()).toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return codeMsg.getCode().equals(value);
+ }
+ }
+
+#### 15.4.5、jetcache
+
+* jetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能
+
+* jetCache设定了本地缓存与远程缓存的多级缓存解决方案
+
+ * 本地缓存(local)
+
+ * LinkedHashMap
+ * Caffeine
+ * 远程缓存(remote)
+
+ * Redis
+ * Tair
+* 加入jetcache坐标
+
+
+ com.alicp.jetcache
+ jetcache-starter-redis
+ 2.6.2
+
+* 配置**远程**缓存必要属性
+
+ jetcache:
+ remote:
+ default:
+ type: redis
+ host: localhost
+ port: 6379
+ poolConfig:
+ maxTotal: 50
jetcache:
+ remote:
+ default:
+ type: redis
+ host: localhost
+ port: 6379
+ poolConfig:
+ maxTotal: 50
+ sms:
+ type: redis
+ host: localhost
+ port: 6379
+ poolConfig:
+ maxTotal: 50
+* 配置**本地**缓存必要属性
+
+ jetcache:
+ local:
+ default:
+ type: linkedhashmap
+ keyConvertor: fastjson
+* 配置范例
+
+ jetcache:
+ statIntervalMinutes: 15
+ areaInCacheName: false
+ local:
+ default:
+ type: linkedhashmap
+ keyConvertor: fastjson
+ limit: 100
+ remote:
+ default:
+ host: localhost
+ port: 6379
+ type: redis
+ keyConvertor: fastjson
+ valueEncoder: java
+ valueDecoder: java
+ poolConfig:
+ minIdle: 5
+ maxIdle: 20
+ maxTotal: 50
+* 配置属性说明
+ 
+
+* 开启jetcache注解支持
+
+ @SpringBootApplication
+ @EnableCreateCacheAnnotation
+ public class Springboot19CacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot19CacheApplication.class, args);
+ }
+ }
+* 声明缓存对象
+
+ @Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @CreateCache(name = "smsCache", expire = 3600)
+ private Cache jetSMSCache;
+ }
+* 操作缓存
+
+ @Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Override
+ public String sendCodeToSMS(String tele) {
+ String code = this.codeUtils.generator(tele);
+ jetSMSCache.put(tele,code);
+ return code;
+ }
+ @Override
+ public boolean checkCode(CodeMsg codeMsg) {
+ String value = jetSMSCache.get(codeMsg.getTele());
+ return codeMsg.getCode().equals(value);
+ }
+ }
+* 启用方法注解
+
+ @SpringBootApplication
+ @EnableCreateCacheAnnotation
+ @EnableMethodCache(basePackages = "com.itheima")
+ public class Springboot20JetCacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot20JetCacheApplication.class, args);
+ }
+ }
+* 使用方法注解操作缓存
+
+ @Service
+ public class BookServiceImpl implements BookService {
+ @Autowired
+ private BookDao bookDao;
+ @Cached(name = "smsCache_", key = "#id", expire = 3600)
+ @CacheRefresh(refresh = 10,timeUnit = TimeUnit.SECONDS)
+ public Book getById(Integer id) {
+ return bookDao.selectById(id);
+ }
+ }
@Service
+ public class BookServiceImpl implements BookService {
+
+ @CacheUpdate(name = "smsCache_", key = "#book.id", value = "#book")
+ public boolean update(Book book) {
+ return bookDao.updateById(book) > 0;
+ }
+
+ @CacheInvalidate(name = "smsCache_", key = "#id")
+ public boolean delete(Integer id) {
+ return bookDao.deleteById(id) > 0;
+ }
+ }
+* 缓存对象必须保障可序列化
+
+ @Data
+ public class Book implements Serializable {
+ }
jetcache:
+ remote:
+ default:
+ type: redis
+ keyConvertor: fastjson
+ valueEncoder: java
+ valueDecoder: java
+* 查看缓存统计报告
+
+ jetcache:
+ statIntervalMinutes: 15
+
+#### 15.4.6、j2cache
+
+* j2cache是一个缓存整合框架,可以提供缓存的整合方案,使各种缓存搭配使用,自身不提供缓存功能
+
+* 基于 ehcache + redis 进行整合
+
+* 加入j2cache坐标,加入整合缓存的坐标
+
+
+ net.oschina.j2cache
+ j2cache-spring-boot2-starter
+ 2.8.0-release
+
+
+ net.oschina.j2cache
+ j2cache-core
+ 2.8.4-release
+
+
+ net.sf.ehcache
+ ehcache
+
+* 配置使用j2cache(application.yml)
+
+ j2cache:
+ config-location: j2cache.properties
+* 配置一级缓存与二级缓存以及一级缓存数据到二级缓存的发送方式(j2cache.properties)
+
+ # 配置1级缓存
+ j2cache.L1.provider_class = ehcache
+ ehcache.configXml = ehcache.xml
+
+ # 配置1级缓存数据到2级缓存的广播方式:可以使用redis提供的消息订阅模式,也可以使用jgroups多播实现
+ j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
+
+ # 配置2级缓存
+ j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
+ j2cache.L2.config_section = redis
+ redis.hosts = localhost:6379
+* 设置使用缓存
+
+ @Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @Autowired
+ private CacheChannel cacheChannel;
+ }
@Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Override
+ public String sendCodeToSMS(String tele) {
+ String code = codeUtils.generator(tele);
+ cacheChannel.set("sms",tele,code);
+ return code;
+ }
+ @Override
+ public boolean checkCode(SMSCode smsCode) {
+ String code = cacheChannel.get("sms",smsCode.getTele()).asString();
+ return smsCode.getCode().equals(code);
+ }
+ }
+
+## 16、定时
+
+任务
+
+* 定时任务是企业级应用中的常见操作
+
+ * 年度报表
+ * 缓存统计报告
+ * … …
+* 市面上流行的定时任务技术
+
+ * Quartz
+ * Spring Task
+
+### 16.1、SpringBoot整合Quartz
+
+* 相关概念
+
+ * 工作(Job):用于定义具体执行的工作
+ * 工作明细(JobDetail):用于描述定时工作相关的信息
+ * 触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定义调度规则
+ * 调度器(Scheduler):描述了工作明细与触发器的对应关系
+* 导入SpringBoot整合quartz的坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-quartz
+
+* 定义具体要执行的任务,继承QuartzJobBean
+
+ public class QuartzTaskBean extends QuartzJobBean {
+ @Override
+ protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
+ System.out.println(“quartz job run... ");
+ }
+ }
+* 定义工作明细与触发器,并绑定对应关系
+
+ @Configuration
+ public class QuartzConfig {
+ @Bean
+ public JobDetail printJobDetail(){
+ return JobBuilder.newJob(QuartzTaskBean.class).storeDurably().build();
+ }
+ @Bean
+ public Trigger printJobTrigger() {
+ CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ?");
+ return TriggerBuilder.newTrigger().forJob(printJobDetail())
+ .withSchedule(cronScheduleBuilder).build();
+ }
+ }
+
+### 16.2、Spring Task
+
+* 开启定时任务功能
+
+ @SpringBootApplication
+ @EnableScheduling
+ public class Springboot22TaskApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot22TaskApplication.class, args);
+ }
+ }
+* 设置定时执行的任务,并设定执行周期
+
+ @Component
+ public class ScheduledBean {
+ @Scheduled(cron = "0/5 * * * * ?")
+ public void printLog(){
+ System.out.println(Thread.currentThread().getName()+":run...");
+ }
+ }
+* 定时任务相关配置
+
+ spring:
+ task:
+ scheduling:
+ # 任务调度线程池大小 默认 1
+ pool:
+ size: 1
+ # 调度线程名称前缀 默认 scheduling-
+ thread-name-prefix: ssm_
+ shutdown:
+ # 线程池关闭时等待所有任务完成
+ await-termination: false
+ # 调度线程关闭前最大等待时间,确保最后一定关闭
+ await-termination-period: 10s
+
+### 16.3、SpringBoot整合JavaMail
+
+* SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于**发送**电子邮件的传输协议
+
+* POP3(Post Office Protocol - Version 3):用于**接收**电子邮件的标准协议
+
+* IMAP(Internet Mail Access Protocol):互联网消息协议,是POP3的替代协议
+
+* 导入SpringBoot整合JavaMail的坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+* 配置JavaMail
+
+ spring:
+ mail:
+ host: smtp.qq.com
+ username: *********@qq.com
+ password: *********
+
+
+
+* 开启定时任务功能
+
+ @Service
+ public class SendMailServiceImpl implements SendMailService {
+ private String from = “********@qq.com"; // 发送人
+ private String to = "********@126.com"; // 接收人
+ private String subject = "测试邮件"; // 邮件主题
+ private String text = "测试邮件正文"; // 邮件内容
+ }
+* 开启定时任务功能
+
+ @Service
+ public class SendMailServiceImpl implements SendMailService {
+ @Autowired
+ private JavaMailSender javaMailSender;
+ @Override
+ public void sendMail() {
+ SimpleMailMessage mailMessage = new SimpleMailMessage();
+ mailMessage.setFrom(from);
+ mailMessage.setTo(to);
+ mailMessage.setSubject(subject);
+ mailMessage.setText(text);
+ javaMailSender.send(mailMessage);
+ }
+ }
+* 附件与HTML文本支持
+
+ private String text = "传智教育";
+ @Override
+ public void sendMail() {
+ try {
+ MimeMessage mimeMessage = javaMailSender.createMimeMessage();
+ MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true);
+ mimeMessageHelper.setFrom(from);
+ mimeMessageHelper.setTo(to);
+ mimeMessageHelper.setSubject(subject);
+ mimeMessageHelper.setText(text,true);
+ File file = new File("logo.png");
+ mimeMessageHelper.addAttachment("美图.png",file);
+ javaMailSender.send(mimeMessage);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+## 17、消息
+
+
+
+* 企业级应用中广泛使用的三种异步消息传递技术
+ * JMS
+ * AMQP
+ * MQTT
+
+### 17.1、JMS
+
+* JMS(Java Message Service):一个规范,等同于JDBC规范,提供了与消息服务相关的API接口
+
+* JMS消息模型
+
+ * peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息。队列的消息只能被一个消费者消费,或超时
+ * **pub**lish-**sub**scribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
+* JMS消息种类
+
+ * TextMessage
+ * MapMessage
+ * **BytesMessage**
+ * StreamMessage
+ * ObjectMessage
+ * Message (只有消息头和属性)
+* JMS实现:ActiveMQ、Redis、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守JMS规范
+
+### 17.2、AMQP
+
+* AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS
+
+* 优点:具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现
+
+* AMQP消息模型
+
+ * direct exchange
+ * fanout exchange
+ * topic exchange
+ * headers exchange
+ * system exchange
+* AMQP消息种类:byte[]
+
+* AMQP实现:RabbitMQ、StormMQ、RocketMQ
+
+### 17.3、MQTT
+
+* MQTT(Message Queueing Telemetry Transport)消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一
+
+### 17.4、Kafka
+
+* Kafka,一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能。
+
+### 17.5、消息案例
+
+* 购物订单业务
+ * 登录状态检测
+ * 生成主单
+ * 生成子单
+ * 库存检测与变更
+ * 积分变更
+ * 支付
+ * 短信通知(异步)
+ * 购物车维护
+ * 运单信息初始化
+ * 商品库存维护
+ * 会员维护
+ * …
+
+### 17.6、ActiveMQ
+
+* 下载地址:[https://activemq.apache.org/components/classic/download](https://activemq.apache.org/components/classic/download/)[/](https://activemq.apache.org/components/classic/download/)
+
+* 安装:解压缩
+
+* 启动服务
+
+ `activemq.bat`
+
+* 访问服务器
+
+ `http://127.0.0.1:8161/`
+
+ * 服务端口:61616,管理后台端口:8161
+ * 用户名&密码:**admin**
+
+#### 17.6.1、SpringBoot整合ActiveMQ
+
+* 导入SpringBoot整合ActiveMQ坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-activemq
+
+* 配置ActiveMQ(采用默认配置)
+
+ spring:
+ activemq:
+ broker-url: tcp://localhost:61616
+ jms:
+ pub-sub-domain: true
+ template:
+ default-destination: itheima
+* 生产与消费消息(使用默认消息存储队列)
+
+ @Service
+ public class MessageServiceActivemqImpl implements MessageService {
+ @Autowired
+ private JmsMessagingTemplate jmsMessagingTemplate;
+ public void sendMessage(String id) {
+ System.out.println("使用Active将待发送短信的订单纳入处理队列,id:"+id);
+ jmsMessagingTemplate.convertAndSend(id);
+ }
+ public String doMessage() {
+ return jmsMessagingTemplate.receiveAndConvert(String.class);
+ }
+ }
+* 生产与消费消息(指定消息存储队列)
+
+ @Service
+ public class MessageServiceActivemqImpl implements MessageService {
+ @Autowired
+ private JmsMessagingTemplate jmsMessagingTemplate;
+
+ public void sendMessage(String id) {
+ System.out.println("使用Active将待发送短信的订单纳入处理队列,id:"+id);
+ jmsMessagingTemplate.convertAndSend("order.sm.queue.id",id);
+ }
+ public String doMessage() {
+ return jmsMessagingTemplate.receiveAndConvert("order.sm.queue.id",String.class);
+ }
+ }
+* 使用消息监听器对消息队列监听
+
+ @Component
+ public class MessageListener {
+ @JmsListener(destination = "order.sm.queue.id")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ }
+* 流程性业务消息消费完转入下一个消息队列
+
+ @Component
+ public class MessageListener {
+ @JmsListener(destination = "order.sm.queue.id")
+ @SendTo("order.other.queue.id")
+ public String receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ return "new:"+id;
+ }
+ }
+
+### 17.7、RabbitMQ
+
+* RabbitMQ基于Erlang语言编写,需要安装Erlang
+
+* Erlang
+
+ * 下载地址:[https](https://www.erlang.org/downloads)[😕/www.erlang.org/downloads](https://www.erlang.org/downloads)
+ * 安装:一键傻瓜式安装,安装完毕需要重启,需要依赖Windows组件
+ * 环境变量配置
+ * ERLANG_HOME
+ * PATH
+* 下载地址:[https://](https://rabbitmq.com/install-windows.html)[rabbitmq.com/install-windows.html](https://rabbitmq.com/install-windows.html)
+
+* 安装:一键傻瓜式安装
+
+* 启动服务
+
+ `rabbitmq-service.bat start`
+
+* 关闭服务
+
+ `rabbitmq-service.bat stop`
+
+* 查看服务状态
+
+ `rabbitmqctl status`
+
+* 服务管理可视化(插件形式)
+
+* 查看已安装的插件列表
+
+* 开启服务管理插件
+
+ `rabbitmq-plugins.bat enable rabbitmq_management`
+
+* 访问服务器
+
+ `http://localhost:15672`
+
+ * 服务端口:5672,管理后台端口:15672
+ * 用户名&密码:**guest**
+
+#### 17.7.1、SpringBoot整合RabbitMQ
+
+* 导入SpringBoot整合RabbitMQ坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+
+* 配置RabbitMQ (采用默认配置)
+
+ spring:
+ rabbitmq:
+ host: localhost
+ port: 5672
+* 定义消息队列(direct)
+
+ @Configuration
+ public class RabbitDirectConfig {
+ @Bean
+ public Queue queue(){
+ return new Queue("simple_queue");
+ }
+ }
@Configuration
+ public class RabbitDirectConfig {
+ @Bean
+ public Queue queue(){
+ // durable:是否持久化,默认false
+ // exclusive:是否当前连接专用,默认false,连接关闭后队列即被删除
+ // autoDelete:是否自动删除,当生产者或消费者不再使用此队列,自动删除
+ return new Queue("simple_queue",true,false,false);
+ }
+ }
@Configuration
+ public class RabbitDirectConfig {
+ @Bean
+ public Queue directQueue(){
+ return new Queue("direct_queue");
+ }
+ @Bean
+ public Queue directQueue2(){
+ return new Queue("direct_queue2");
+ }
+ @Bean
+ public DirectExchange directExchange(){
+ return new DirectExchange("directExchange");
+ }
+ @Bean
+ public Binding bindingDirect(){
+ return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
+ }
+ @Bean
+ public Binding bindingDirect2(){
+ return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
+ }
+ }
+* 生产与消费消息(direct)
+
+ @Service
+ public class MessageServiceRabbitmqDirectImpl implements MessageService {
+ @Autowired
+ private AmqpTemplate amqpTemplate;
+ @Override
+ public void sendMessage(String id) {
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ amqpTemplate.convertAndSend("directExchange","direct",id);
+ }
+ }
+* 使用消息监听器对消息队列监听(direct)
+
+ @Component
+ public class RabbitMessageListener {
+ @RabbitListener(queues = "direct_queue")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ }
+* 使用多消息监听器对消息队列监听进行消息轮循处理(direct)
+
+ @Component
+ public class RabbitMessageListener2 {
+ @RabbitListener(queues = "direct_queue")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务(two),id:"+id);
+ }
+ }
+* 定义消息队列(topic)
+
+ @Configuration
+ public class RabbitTopicConfig {
+ @Bean
+ public Queue topicQueue(){ return new Queue("topic_queue"); }
+ @Bean
+ public Queue topicQueue2(){ return new Queue("topic_queue2"); }
+ @Bean
+ public TopicExchange topicExchange(){
+ return new TopicExchange("topicExchange");
+ }
+ @Bean
+ public Binding bindingTopic(){
+ return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.*");
+ }
+ @Bean
+ public Binding bindingTopic2(){
+ return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.#");
+ }
+ }
+* 绑定键匹配规则
+
+ * `*`(星号): 用来表示一个单词 ,且该单词是必须出现的
+ * `#`(井号): 用来表示任意数量
+
+
+
+* 生产与消费消息(topic)
+
+ @Service
+ public class MessageServiceRabbitmqTopicmpl implements MessageService {
+ @Autowired
+ private AmqpTemplate amqpTemplate;
+ @Override
+ public void sendMessage(String id) {
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ amqpTemplate.convertAndSend("topicExchange","topic.order.id",id);
+ }
+ }
+* 使用消息监听器对消息队列监听(topic)
+
+ @Component
+ public class RabbitTopicMessageListener {
+ @RabbitListener(queues = "topic_queue")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ @RabbitListener(queues = "topic_queue2")
+ public void receive2(String id){
+ System.out.println("已完成短信发送业务(two),id:"+id);
+ }
+ }
+
+### 17.8、RocketMQ
+
+* 下载地址:[https://rocketmq.apache.org](https://rocketmq.apache.org/)[/](https://rocketmq.apache.org/)
+
+* 安装:解压缩
+
+ * 默认服务端口:9876
+* 环境变量配置
+
+* ROCKETMQ_HOME
+
+* PATH
+
+* NAMESRV_ADDR (建议): 127.0.0.1:9876
+
+* 命名服务器与broker
+ 
+
+* 启动命名服务器
+
+ `mqnamesrv`
+
+* 启动broker
+
+ `mqbroker`
+
+* 服务器功能测试:生产者
+
+ `tools org.apache.rocketmq.example.quickstart.Producer`
+
+* 服务器功能测试:消费者
+
+ `tools org.apache.rocketmq.example.quickstart.Consumer`
+
+#### 17.8.1、SpringBoot整合RocketMQ
+
+* 导入SpringBoot整合RocketMQ坐标
+
+
+ org.apache.rocketmq
+ rocketmq-spring-boot-starter
+ 2.2.1
+
+
+* 配置RocketMQ (采用默认配置)
+
+ rocketmq:
+ name-server: localhost:9876
+ producer:
+ group: group_rocketmq
+* 生产消息
+
+ @Service
+ public class MessageServiceRocketmqImpl implements MessageService {
+ @Autowired
+ private RocketMQTemplate rocketMQTemplate;
+ @Override
+ public void sendMessage(String id) {
+ rocketMQTemplate.convertAndSend("order_sm_id",id);
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ }
+ }
+* 生产异步消息
+
+ @Service
+ public class MessageServiceRocketmqImpl implements MessageService {
+ @Autowired
+ private RocketMQTemplate rocketMQTemplate;
+ @Override
+ public void sendMessage(String id) {
+ SendCallback callback = new SendCallback() {
+ @Override
+ public void onSuccess(SendResult sendResult) {
+ System.out.println("消息发送成功");
+ }
+ @Override
+ public void onException(Throwable throwable) {
+ System.out.println("消息发送失败!!!!!!!!!!!");
+ }
+ };
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ rocketMQTemplate.asyncSend("order_sm_id",id,callback);
+ }
+ }
+* 使用消息监听器对消息队列监听
+
+ @Component
+ @RocketMQMessageListener(topic="order_sm_id",consumerGroup = "group_rocketmq")
+ public class RocketmqMessageListener implements RocketMQListener {
+ @Override
+ public void onMessage(String id) {
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ }
+
+### 17.9、Kafka
+
+* 下载地址:[https://](https://kafka.apache.org/downloads)[kafka.apache.org/downloads](https://kafka.apache.org/downloads)
+
+* windows 系统下3.0.0版本存在BUG,建议使用2.X版本
+
+* 安装:解压缩
+
+* 启动zookeeper
+
+ `zookeeper-server-start.bat ..\..\config\zookeeper.properties`
+
+ * 默认端口:2181
+* 启动kafka
+
+ `kafka-server-start.bat ..\..\config\server.properties`
+
+ * 默认端口:9092
+* 创建topic
+
+ `kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic itheima`
+
+* 查看topic
+
+ `kafka-topics.bat --zookeeper 127.0.0.1:2181 --list`
+
+* 删除topic
+
+ `kafka-topics.bat --delete --zookeeper localhost:2181 --topic itheima`
+
+* 生产者功能测试
+
+ `kafka-console-producer.bat --broker-list localhost:9092 --topic itheima`
+
+* 消费者功能测试
+
+ `kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic itheima --from-beginning`
+
+#### 17.9.1、SpringBoot整合Kafka
+
+* 导入SpringBoot整合Kafka坐标
+
+
+ org.springframework.kafka
+ spring-kafka
+
+* 配置Kafka (采用默认配置)
+
+ spring:
+ kafka:
+ bootstrap-servers: localhost:9092
+ consumer:
+ group-id: order
+* 生产消息
+
+ @Service
+ public class MessageServiceKafkaImpl implements MessageService {
+ @Autowired
+ private KafkaTemplate kafkaTemplate;
+ @Override
+ public void sendMessage(String id) {
+ System.out.println("使用Kafka将待发送短信的订单纳入处理队列,id:"+id);
+ kafkaTemplate.send("kafka_topic",id); }
+ }
+* 使用消息监听器对消息队列监听
+
+ @Component
+ public class KafkaMessageListener{
+ @KafkaListener(topics = {"kafka_topic"})
+ public void onMessage(ConsumerRecord, ?> record) {
+ System.out.println("已完成短信发送业务,id:"+record.value());
+ }
+ }
+
+## 18、监控
+
+### 18.1、简介
+
+监控的意义:
+
+* 监控服务状态是否宕机
+* 监控服务运行指标(内存、虚拟机、线程、请求等)
+* 监控日志
+* 管理服务(服务下线)
+
+监控的实施方式:
+
+* 显示监控信息的服务器:用于获取服务信息,并显示对应的信息
+* 运行的服务:启动时主动上报,告知监控服务器自己需要受到监控
+ 
+
+### 18.2、可视化监控平台
+
+* Spring Boot Admin,开源社区项目,用于管理和监控SpringBoot应用程序。 客户端注册到服务端后,通过HTTP请求方式,服务端定期从客户端获取对应的信息,并通过UI界面展示对应信息。
+
+* Admin服务端
+
+
+ 2.5.4
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-server
+
+
+
+
+
+ de.codecentric
+ spring-boot-admin-dependencies
+ ${spring-boot-admin.version}
+ pom
+ import
+
+
+
+* Admin客户端
+
+
+ 2.5.4
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+
+
+
+
+
+ de.codecentric
+ spring-boot-admin-dependencies
+ ${spring-boot-admin.version}
+ pom
+ import
+
+
+
+* Admin服务端
+
+
+ de.codecentric
+ spring-boot-admin-starter-server
+ 2.5.4
+
+* Admin客户端
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+ 2.5.4
+
+* Admin服务端
+
+ server:
+ port: 8080
+* 设置启用Spring-Admin
+
+ @SpringBootApplication
+ @EnableAdminServer
+ public class Springboot25ActuatorServerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot25ActuatorServerApplication.class, args);
+ }
+ }
+* Admin客户端
+
+ spring:
+ boot:
+ admin:
+ client:
+ url: http://localhost:8080
+ management:
+ endpoint:
+ health:
+ show-details: always
+ endpoints:
+ web:
+ exposure:
+ include: "*"
+
+
+
+### 18.3、监控原理
+
+* Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
+
+* 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
+
+* 访问当前应用所有端点信息:**/actuator**
+
+* 访问端点详细信息:/actuator**/****端点名称**
+ 
+
+
+
+* Web程序专用端点
+ 
+
+* 启用指定端点
+
+ management:
+ endpoint:
+ health: # 端点名称
+ enabled: true show-details: always beans: # 端点名称 enabled: true
+* 启用所有端点
+
+ management:
+ endpoints:
+ enabled-by-default: true
+* 暴露端点功能
+
+ * 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息
+ 
+ 
+ 
+
+### 18.4、自定义监控指标
+
+* 为info端点添加自定义指标
+
+ info:
+ appName: @project.artifactId@
+ version: @project.version@
+ author: itheima
@Component
+ public class AppInfoContributor implements InfoContributor {
+ @Override
+ public void contribute(Info.Builder builder) {
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ }
+ }
+* 为Health端点添加自定义指标
+
+ @Component
+ public class AppHealthContributor extends AbstractHealthIndicator {
+ @Override
+ protected void doHealthCheck(Health.Builder builder) throws Exception {
+ boolean condition = true;
+ if(condition){
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ builder.status(Status.UP);
+ }else{
+ builder.status(Status.DOWN);
+ }
+ }
+ }
+* 为Metrics端点添加自定义指标
+
+ @Service
+ public class BookServiceImpl extends ServiceImpl implements IBookService {
+ private Counter counter;
+ public BookServiceImpl(MeterRegistry meterRegistry){
+ counter = meterRegistry.counter("用户付费操作次数:");
+ }
+ @Override
+ public boolean delete(Integer id) {
+ counter.increment();
+ return bookDao.deleteById(id) > 0;
+ }
+ }
+* 自定义端点
+
+ @Component
+ @Endpoint(id="pay")
+ public class PayEndPoint {
+ @ReadOperation
+ public Object getPay(){
+ //调用业务操作,获取支付相关信息结果,最终return出去
+ Map payMap = new HashMap();
+ payMap.put("level 1",103);
+ payMap.put("level 2",315);
+ payMap.put("level 3",666);
+ return payMap;
+ }
+ }
+
+pom
+import
+
+ - Admin客户端
+
+```xml
+
+ 2.5.4
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+
+
+
+
+
+ de.codecentric
+ spring-boot-admin-dependencies
+ ${spring-boot-admin.version}
+ pom
+ import
+
+
+
+
+* Admin服务端
+
+
+ de.codecentric
+ spring-boot-admin-starter-server
+ 2.5.4
+
+* Admin客户端
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+ 2.5.4
+
+* Admin服务端
+
+ server:
+ port: 8080
+* 设置启用Spring-Admin
+
+ @SpringBootApplication
+ @EnableAdminServer
+ public class Springboot25ActuatorServerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot25ActuatorServerApplication.class, args);
+ }
+ }
+* Admin客户端
+
+ spring:
+ boot:
+ admin:
+ client:
+ url: http://localhost:8080
+ management:
+ endpoint:
+ health:
+ show-details: always
+ endpoints:
+ web:
+ exposure:
+ include: "*"
+
+ [外链图片转存中…(img-cXWfQSEx-1657811363485)]
+
+### 18.3、监控原理
+
+* Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
+
+* 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
+
+* 访问当前应用所有端点信息:**/actuator**
+
+* 访问端点详细信息:/actuator**/****端点名称**
+
+ [外链图片转存中…(img-KSdMaD18-1657811363486)]
+
+ [外链图片转存中…(img-UlUbALwe-1657811363487)]
+
+* Web程序专用端点
+
+ [外链图片转存中…(img-maGlhCAb-1657811363487)]
+
+* 启用指定端点
+
+ management:
+ endpoint:
+ health: # 端点名称
+ enabled: true show-details: always beans: # 端点名称 enabled: true
+* 启用所有端点
+
+ management:
+ endpoints:
+ enabled-by-default: true
+* 暴露端点功能
+
+ * 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息
+
+ [外链图片转存中…(img-6UeTKYgJ-1657811363488)]
+
+[外链图片转存中…(img-bLPYJP4S-1657811363489)]
+
+[外链图片转存中…(img-RwCI70cm-1657811363490)]
+
+### 18.4、自定义监控指标
+
+* 为info端点添加自定义指标
+
+ info:
+ appName: @project.artifactId@
+ version: @project.version@
+ author: itheima
@Component
+ public class AppInfoContributor implements InfoContributor {
+ @Override
+ public void contribute(Info.Builder builder) {
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ }
+ }
+* 为Health端点添加自定义指标
+
+ @Component
+ public class AppHealthContributor extends AbstractHealthIndicator {
+ @Override
+ protected void doHealthCheck(Health.Builder builder) throws Exception {
+ boolean condition = true;
+ if(condition){
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ builder.status(Status.UP);
+ }else{
+ builder.status(Status.DOWN);
+ }
+ }
+ }
+* 为Metrics端点添加自定义指标
+
+ @Service
+ public class BookServiceImpl extends ServiceImpl implements IBookService {
+ private Counter counter;
+ public BookServiceImpl(MeterRegistry meterRegistry){
+ counter = meterRegistry.counter("用户付费操作次数:");
+ }
+ @Override
+ public boolean delete(Integer id) {
+ counter.increment();
+ return bookDao.deleteById(id) > 0;
+ }
+ }
+* 自定义端点
+
+ @Component
+ @Endpoint(id="pay")
+ public class PayEndPoint {
+ @ReadOperation
+ public Object getPay(){
+ //调用业务操作,获取支付相关信息结果,最终return出去
+ Map payMap = new HashMap();
+ payMap.put("level 1",103);
+ payMap.put("level 2",315);
+ payMap.put("level 3",666);
+ return payMap;
+ }
+ }
\ No newline at end of file
diff --git "a/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md" "b/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md"
index 0aa0960..5c926ef 100644
--- "a/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md"
+++ "b/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md"
@@ -16,7 +16,7 @@
- [sleep和wait的区别](#sleep和wait的区别)
- [java 地址和值传递的例子](#java-地址和值传递的例子)
- [Java序列化](#java序列化)
- - [java NIO,java 多线程、线程池,java 网络编程解决并发量](#java-niojava-多线程线程池java-网络编程解决并发量)
+ - [java NIO,java 多线程、线程池,java 网络编程解决并发量](#java-niojava-多线程-线程池java-网络编程解决并发量)
- [JDBC 连接的过程 ,手写 jdbc 连接过程](#jdbc-连接的过程-手写-jdbc-连接过程)
- [说出三个遇到过的程序报异常的情况](#说出三个遇到过的程序报异常的情况)
- [socket 是靠什么协议支持的](#socket-是靠什么协议支持的)
@@ -41,7 +41,7 @@
- [concurrenthashmap的jdk1.7和jdk1.8区别](#concurrenthashmap的jdk17和jdk18区别)
- [HashMap 实现原理,扩容因子过大过小的缺点,扩容过程 采用什么方法能保证每个 bucket 中的数据更均匀 解决冲突的方式,还有没有其他方式(全域哈希)](#hashmap-实现原理扩容因子过大过小的缺点扩容过程-采用什么方法能保证每个-bucket-中的数据更均匀-解决冲突的方式还有没有其他方式全域哈希)
- [Collection 集合类中只能在 Iterator 中删除元素的原因](#collection-集合类中只能在-iterator-中删除元素的原因)
- - [ArrayList、LinkedList、Vector](#arraylistlinkedlistvector)
+ - [ArrayList、LinkedList、Vector](#arraylist-linkedlist-vector)
- [还了解除 util 其他包下的 List 吗?](#还了解除-util-其他包下的-list-吗)
- [CopyOnWriteArrayList](#copyonwritearraylist)
- [ConcurrentHashMap 和 LinkedHashMap 差异和适用情形](#concurrenthashmap-和-linkedhashmap-差异和适用情形)
@@ -77,7 +77,7 @@
- [java 线程安全都体现在哪些方面](#java-线程安全都体现在哪些方面)
- [如果维护线程安全 如果想实现一个线程安全的队列,可以怎么实现?](#如果维护线程安全-如果想实现一个线程安全的队列可以怎么实现)
- [Java多线程通信方式](#java多线程通信方式)
- - [CountDownLatch、CyclicBarrier、Semaphore 用法总结](#countdownlatchcyclicbarriersemaphore-用法总结)
+ - [CountDownLatch、CyclicBarrier、Semaphore 用法总结](#countdownlatch-cyclicbarrier-semaphore-用法总结)
- [juc下的内容](#juc下的内容)
- [AOS等并发相关面试题](#aos等并发相关面试题)
- [threadlocal](#threadlocal)
@@ -129,8 +129,8 @@
- [数据库死锁问题](#数据库死锁问题)
- [hash索引和B+树索引的区别](#hash索引和b树索引的区别)
- [可重复的原理MVCC](#可重复的原理mvcc)
- - [count(1)、count(*)、count(列名)](#count1countcount列名)
- - [mysql的undo、redo、binlog的区别](#mysql的undoredobinlog的区别)
+ - [count(1)、count(*)、count(列名)](#count1-count-count列名)
+ - [mysql的undo、redo、binlog的区别](#mysql的undo-redo-binlog的区别)
- [explain解释](#explain解释)
- [mysql分页查询优化](#mysql分页查询优化)
- [sql注入](#sql注入)
diff --git "a/docs/java/Basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/Basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md"
new file mode 100644
index 0000000..5a97f4b
--- /dev/null
+++ "b/docs/java/Basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md"
@@ -0,0 +1 @@
+https://juejin.cn/post/6844904127059738631
\ No newline at end of file
diff --git a/docs/java/IO/java IO.md b/docs/java/IO/java IO.md
new file mode 100644
index 0000000..c197d76
--- /dev/null
+++ b/docs/java/IO/java IO.md
@@ -0,0 +1 @@
+https://juejin.cn/post/6844904125700784136
\ No newline at end of file
diff --git "a/docs/java/JavaFamily/\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md" "b/docs/java/JavaFamily/\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..459cd09
--- /dev/null
+++ "b/docs/java/JavaFamily/\351\235\242\350\257\225\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
@@ -0,0 +1 @@
+https://github.com/AobingJava/JavaFamily
\ No newline at end of file
diff --git "a/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\200\345\255\243\347\254\224\350\256\260.md" "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\200\345\255\243\347\254\224\350\256\260.md"
new file mode 100644
index 0000000..6052a3f
--- /dev/null
+++ "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\200\345\255\243\347\254\224\350\256\260.md"
@@ -0,0 +1,2 @@
+https://www.yuque.com/books/share/327d9543-85d2-418f-9315-41c3e19d2768/0dca325c876a0e85f0ba4ea48042e61d
+https://github.com/shishan100/Java-Interview-Advanced#%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97
\ No newline at end of file
diff --git "a/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\211\345\255\243\347\254\224\350\256\260.md" "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\211\345\255\243\347\254\224\350\256\260.md"
new file mode 100644
index 0000000..42773c7
--- /dev/null
+++ "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\270\211\345\255\243\347\254\224\350\256\260.md"
@@ -0,0 +1 @@
+https://blog.csdn.net/u013073869/article/details/105271345
\ No newline at end of file
diff --git "a/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\272\214\345\255\243\347\254\224\350\256\260.md" "b/docs/java/Java\345\267\245\347\250\213\345\270\210\351\235\242\350\257\225\347\252\201\345\207\273/\347\254\254\344\272\214\345\255\243\347\254\224\350\256\260.md"
new file mode 100644
index 0000000..e69de29
diff --git "a/docs/java/Multithread/Java\345\271\266\345\217\221.md" "b/docs/java/Multithread/Java\345\271\266\345\217\221.md"
new file mode 100644
index 0000000..4a068da
--- /dev/null
+++ "b/docs/java/Multithread/Java\345\271\266\345\217\221.md"
@@ -0,0 +1,2 @@
+参考:https://juejin.cn/post/6844904063687983111
+参考:https://www.cmsblogs.com/category/1391296887813967872
\ No newline at end of file
diff --git "a/docs/java/collection/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md" "b/docs/java/collection/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md"
new file mode 100644
index 0000000..85a698b
--- /dev/null
+++ "b/docs/java/collection/Java\351\233\206\345\220\210\351\235\242\350\257\225\351\242\230.md"
@@ -0,0 +1,114 @@
+### 集合面试题
+
+> ArrayList、LinkedList和Vector的区别和实现原理
+
+#### 数据结构实现
+
+ArrayList和Vector都是基于可改变大小的数据实现的,而LinkedList是基于双链表实现的。
+
+#### 增删改查效率对比
+
+ArrayList和Vector都是基于可改变大小的数据实现的,因此,从指定的位置检索对象时,或在集合的末尾插入对象、删除一个对象的时间都是O(1),但是如果在其他位置增加或者删除对象,花费的时间是O(n);
+
+而LinkedList是基于双链表实现的,因此,在插入、删除集合中的任何位置上的对象,所花费的时间都是O(1),但基于链表的数据结构在查找元素时的效率是更低的,花费的时间为O(n)。
+
+因此,从以上分析我们可以知道,查找特定的对象或者在集合末端增加或者删除对象,ArrayList和Vector的效率是ok的,如果在指定的位置删除或者插入,LinkedList的效率则更高。
+
+#### 线程安全
+
+ArrayList、LinkedList不具有线程安全性,在多线程的问题下是不能使用的,如果想要在多线程的环境下使用怎么办呢?我们可以采用Collections的静态方法synchronizedList包装一下,就可以保证线程安全了,但是在实际情况下,并不会使用这种方式,而是会采用更高级的集合进行线程安全的操作。
+
+Vector是线程安全的,其保证线程安全的机制是采用synchronized关键字,我们都知道,这个关键字的效率是不高的,在后续的很多版本中,线程安全的机制都不会采用这种方式,因此,Vector的效率是比ArrayList、LinkedList更低效的。
+
+#### 扩容机制
+
+ArrayList和Vector都是基于数据这种数据结构实现的,因此,在集合的容量满了时,是需要进行扩容操作的。
+
+在扩容时,ArrayList扩容后的容量是原先的1.5倍,扩容后,再将原先的数组中的数据拷贝到新建的数组中。
+
+Vector默认情况下,扩容后的容量是原先的2倍,除此之外,Vector还有一种可以设置**容量增量**的机制,在Vector中有capacityIncrement变量用于控制扩容时的增量,具体的规则是:当capacityIncrement大于0时,扩容时增加的大小就是capacityIncrement的大小,如果capacityIncrement小于等于0时,则将容量增加为之前的2倍。
+
+> HashMap原理分析
+
+在分析HashMap的原理之前,先说明一下,大家应该都知道HashMap在JDK1.7和1.8的实现上是有较大的区别的,而面试官也是非常喜欢考察这一个点,因此,这里也是采用这两个JDK版本对比来进行分析,这样也可以印象更加深刻一些。
+
+#### 数据结构
+
+在数据结构的实现上,大家应该都知道,JDK1.7是数组+单链表的形式,而1.8采用的是数组+单链表+红黑树,具体的表现如下:
+
+|版本|数据结构|数组+链表的实现形式|红黑树实现形式|
+|-|-|-|-|
+|JDK1.8|数组+单链表+红黑树|Node|TreeNode|
+|JDK1.7|数组+单链表|Entry|-|
+
+为了更好的让大家理解后续的讲解,这里先讲解一下HashMap中实现的一些重要参数。
+
+- 容量(capacity): HashMap中数组的长度
+ - 容量范围:必须是 2 的幂
+ - 初始容量 = 哈希表创建时的容量
+ - 默认容量 = 16 = 1<<4 = 00001中的1向左移4位 = 10000 = 十进制的 2^4 = 16
+ `static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;`
+ - 最大容量 = 2的30次方
+ `static final int MAXIMUM_CAPACITY = 1 << 30;`
+
+- 加载因子(Load factor):HashMap在其容量自动增加时,会设置加载因子,当达到设置的值时,就会触发自动扩容。
+ - 加载因子越大、填满的元素越多,也就是说,空间利用率高、但冲突的机会加大、查找效率变低
+ - 加载因子越小、填满的元素越少,也就是说,空间利用率小、冲突的机会减小、查找效率高
+ // 实际加载因子
+ `final float loadFactor;`
+ // 默认加载因子 = 0.75
+ `static final float DEFAULT_LOAD_FACTOR = 0.75f;`
+
+- 扩容阈值(threshold):当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的容量)。
+ - 扩容 = 对哈希表进行resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
+ - 扩容阈值 = 容量 x 加载因子
+
+#### 获取数据(get)
+
+HashMap的获取数据的过程大致如下:
+
+- 首先,根据key判断是否为空值;
+- 如果为空,则到hashmap数组的第1个位置,寻找对应key为null的键;
+- 如果不为空,则根据key计算hash值;
+- 根据得到的hash值采用`hash & (length - 1)`的计算方式得到key在数组中的位置;
+- 结束。
+
+以上就是大致的数据获取流程,接下来,我们再对JDK1.7和1.8获取数据的细节做一个对比。
+
+|版本|hash值的计算方式|
+|-|-|
+|JDK1.8|1、hash = (key == null) ? 0 : hash(key); private static final Protocol protocol =
+ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtensi
+on();
+复制代码
+
+* 工厂模式
+
+ Provider 在 export 服务时,会调用 ServiceConfig 的 export 方法。ServiceConfig中有个字段:
+
+private static final Protocol protocol =
+ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtensi
+on();
+复制代码
+
+Dubbo 里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了 JDKSPI 的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在 classpath下增加个文件就可以了,代码零侵入。另外,像上面的 Adaptive 实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。
+
+* **装饰器模式**
+
+ Dubbo 在启动和调用阶段都大量使用了装饰器模式。以 Provider 提供的调用链为例,具体的调用链代码是在 ProtocolFilterWrapper 的 buildInvokerChain 完成的,具体是将注解中含有 group=provider 的 Filter 实现,按照 order 排序,最后的调用顺序是:
+
+EchoFilter -> ClassLoaderFilter -> GenericFilter -> ContextFilter ->
+ExecuteLimitFilter -> TraceFilter -> TimeoutFilter -> MonitorFilter ->
+ExceptionFilter
+复制代码
+
+更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter 的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter 则只是在主功能上添加了功能,更改当前线程的 ClassLoader,这是典型的装饰器模式。
+
+* **观察者模式**
+
+ Dubbo 的 Provider 启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个 listener。注册中心会每 5 秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个 notify 消息,provider 接受到 notify 消息后,运行 NotifyListener 的 notify 方法,执行监听器方法。
+
+* **动态代理模式**
+
+ Dubbo 扩展 JDK SPI 的类 ExtensionLoader 的 Adaptive 实现是典型的动态代理实现。Dubbo 需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是 ExtensionLoader 的 createAdaptiveExtensionClassCode 方法。代理类主要逻辑是,获取 URL 参数中指定参数的值作为获取实现类的 key。
+
+## 运维管理
+
+### 服务上线怎么兼容旧版本?
+
+* 可以用版本号(version)过渡,多个不同版本的服务注册到注册中心,版本号不同的服务相互间不引用。这个和服务分组的概念有一点类似。
+
+### Dubbo telnet 命令能做什么?
+
+* dubbo 服务发布之后,我们可以利用 telnet 命令进行调试、管理。Dubbo2.0.5 以上版本服务提供端口支持 telnet 命令
+
+### Dubbo 支持服务降级吗?
+
+* 以通过 dubbo:reference 中设置 mock=“return null”。mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+Mock” 后缀。然后在 Mock 类里实现自己的降级逻辑
+
+### Dubbo 如何优雅停机?
+
+* Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才会执行。
+
+## SPI
+
+### Dubbo SPI 和 Java SPI 区别?
+
+* JDK SPI:
+
+ JDK 标准的 SPI 会一次性加载所有的扩展实现,如果有的扩展很耗时,但也没用上,很浪费资源。所以只希望加载某个的实现,就不现实了
+
+* DUBBO SPI:
+
+ 1、对 Dubbo 进行扩展,不需要改动 Dubbo 的源码
+
+ 2、延迟加载,可以一次只加载自己想要加载的扩展实现。
+
+ 3、增加了对扩展点 IOC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
+
+ 4、Dubbo 的扩展机制能很好的支持第三方 IoC 容器,默认支持 Spring Bean。
+
+## 其他
+
+### Dubbo 支持分布式事务吗?
+
+* 目前暂时不支持,可与通过 tcc-transaction 框架实现
+
+* 介绍:tcc-transaction 是开源的 TCC 补偿性分布式事务框架
+
+* TCC-Transaction 通过 Dubbo 隐式传参的功能,避免自己对业务代码的入侵。
+
+### Dubbo 可以对结果进行缓存吗?
+
+* 为了提高数据访问的速度。Dubbo 提供了声明式缓存,以减少用户加缓存的工作量
+ io.netty
+ netty-all
+ 4.1.16.Final
+
+```
+
+* 2、NettyServer 模板,看起来代码那么多,`其实只需要添加一行消息就好了`
+* `请认真看中间的代码`
+
+```java
+package com.lijie.iob;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.serialization.ClassResolvers;
+import io.netty.handler.codec.serialization.ObjectEncoder;
+import io.netty.handler.codec.string.StringDecoder;
+
+public class NettyServer {
+ public static void main(String[] args) throws InterruptedException {
+ EventLoopGroup bossGroup = new NioEventLoopGroup();
+ EventLoopGroup workerGroup = new NioEventLoopGroup();
+ try {
+ ServerBootstrap b = new ServerBootstrap();
+ b.group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .childHandler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(SocketChannel socketChannel) throws Exception {
+ ChannelPipeline pipeline = socketChannel.pipeline();
+ pipeline.addLast(new StringDecoder());
+ pipeline.addLast("encoder", new ObjectEncoder());
+ pipeline.addLast(" decoder", new io.netty.handler.codec.serialization.ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
+
+ //重点,其他的都是复用的
+ //这是真正的I0的业务代码,把他封装成一个个的个Hand1e类就行了
+ //把他当成 SpringMVC的Controller
+ pipeline.addLast(new NettyServerHandler());
+
+ }
+ })
+ .option(ChannelOption.SO_BACKLOG, 128)
+ .childOption(ChannelOption.SO_KEEPALIVE, true);
+ ChannelFuture f = b.bind(8000).sync();
+ System.out.println("服务端启动成功,端口号为:" + 8000);
+ f.channel().closeFuture().sync();
+ } finally {
+ workerGroup.shutdownGracefully();
+ bossGroup.shutdownGracefully();
+ }
+ }
+}
+```
+
+* 3、需要做的IO操作,重点是继承ChannelInboundHandlerAdapter类就好了
+
+```java
+package com.lijie.iob;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+public class NettyServerHandler extends ChannelInboundHandlerAdapter {
+ RequestHandler requestHandler = new RequestHandler();
+
+ @Override
+ public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+ Channel channel = ctx.channel();
+ System.out.println(String.format("客户端信息: %s", channel.remoteAddress()));
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ Channel channel = ctx.channel();
+ String request = (String) msg;
+ System.out.println(String.format("客户端发送的消息 %s : %s", channel.remoteAddress(), request));
+ String response = requestHandler.handle(request);
+ ctx.write(response);
+ ctx.flush();
+ }
+}
+```
+
+* 4 客户的代码还是之前NIO的代码,我在复制下来一下吧
+
+```java
+package com.test.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.util.Scanner;
+
+//TCP协议Socket:客户端
+public class Client01 {
+ public static void main(String[] args) throws IOException {
+ //创建套接字对象socket并封装ip与port
+ Socket socket = new Socket("127.0.0.1", 8000);
+ //根据创建的socket对象获得一个输出流
+ OutputStream outputStream = socket.getOutputStream();
+ //控制台输入以IO的形式发送到服务器
+ System.out.println("TCP连接成功 \n请输入:");
+ while(true){
+ byte[] car = new Scanner(System.in).nextLine().getBytes();
+ outputStream.write(car);
+ System.out.println("TCP协议的Socket发送成功");
+ //刷新缓冲区
+ outputStream.flush();
+ }
+ }
+}
+```
+
+* 运行测试,还是之前那样,启动服务端,在启动所有客户端控制台输入就好了:
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904125700784136
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\237\272\347\241\200.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\237\272\347\241\200.md"
new file mode 100644
index 0000000..e4df817
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\237\272\347\241\200.md"
@@ -0,0 +1,1313 @@
+
+
+## Java概述
+
+### 何为编程
+
+* 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。
+
+* 为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。
+
+### 什么是Java
+
+* Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
+
+### jdk1.5之后的三大版本
+
+* Java SE(J2SE,Java 2 Platform Standard Edition,标准版)
+ Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的 Java 应用程序。Java SE 包含了支持 Java Web 服务开发的类,并为Java EE和Java ME提供基础。
+* Java EE(J2EE,Java 2 Platform Enterprise Edition,企业版)
+ Java EE 以前称为 J2EE。企业版本帮助开发和部署可移植、健壮、可伸缩且安全的服务器端Java 应用程序。Java EE 是在 Java SE 的基础上构建的,它提供 Web 服务、组件模型、管理和通信 API,可以用来实现企业级的面向服务体系结构(service-oriented architecture,SOA)和 Web2.0应用程序。2018年2月,Eclipse 宣布正式将 JavaEE 更名为 JakartaEE
+* Java ME(J2ME,Java 2 Platform Micro Edition,微型版)
+ Java ME 以前称为 J2ME。Java ME 为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用程序提供一个健壮且灵活的环境。Java ME 包括灵活的用户界面、健壮的安全模型、许多内置的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于 Java ME 规范的应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能。
+
+### 3 Jdk和Jre和JVM的区别
+
+`看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM`
+
+* JDK :Jdk还包括了一些Jre之外的东西 ,就是这些东西帮我们编译Java代码的, 还有就是监控Jvm的一些工具 Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等
+
+* JRE :Jre大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库 Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包
+
+ 如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
+
+* Jvm:在倒数第二层 由他可以在(最后一层的)各种平台上运行 Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。
+
+
+### 什么是跨平台性?原理是什么
+
+* 所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。
+
+* 实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。
+
+### Java语言有哪些特点
+
+* 简单易学(Java语言的语法与C语言和C++语言很接近)
+
+* 面向对象(封装,继承,多态)
+
+* 平台无关性(Java虚拟机实现平台无关性)
+
+* 支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的)
+
+* 支持多线程(多线程机制使应用程序在同一时间并行执行多项任)
+
+* 健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)
+
+* 安全性好
+
+### 什么是字节码?采用字节码的最大好处是什么
+
+* **字节码**:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
+
+* **采用字节码的好处**:
+
+ Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
+
+* **先看下java中的编译器和解释器**:
+
+ Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。
+
+ Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
+
+### 什么是Java程序的主类?应用程序和小程序的主类有何不同?
+
+* 一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。
+
+### Java应用程序与小程序之间有那些差别?
+
+* 简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。
+
+### Java和C++的区别
+
+`我知道很多人没学过C++,但是面试官就是没事喜欢拿咱们Java和C++比呀!没办法!!!就算没学过C++,也要记下来!`
+
+* 都是面向对象的语言,都支持封装、继承和多态
+* Java不提供指针来直接访问内存,程序内存更加安全
+* Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
+* Java有自动内存管理机制,不需要程序员手动释放无用内存
+
+### Oracle JDK 和 OpenJDK 的对比
+
+1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次;
+
+2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的;
+
+3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题;
+
+4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能;
+
+5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
+
+6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。
+
+## 基础语法
+
+### 数据类型
+
+#### Java有哪些数据类型
+
+**定义**:Java语言是强类型语言,对于每一种数据都定义了明确的具体的数据类型,在内存中分配了不同大小的内存空间。
+
+**分类**
+
+* 基本数据类型
+ * 数值型
+ * 整数类型(byte,short,int,long)
+ * 浮点类型(float,double)
+ * 字符型(char)
+ * 布尔型(boolean)
+* 引用数据类型
+ * 类(class)
+ * 接口(interface)
+ * 数组([])
+
+**Java基本数据类型图**
+
+
+#### switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上
+
+* 在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
+
+#### 用最有效率的方法计算 2 乘以 8
+
+* 2 << 3(左移 3 位相当于乘以 2 的 3 次方,右移 3 位相当于除以 2 的 3 次方)。
+
+#### Math.round(11.5) 等于多少?Math.round(-11.5)等于多少
+
+* Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。
+
+#### float f=3.4;是否正确
+
+* 不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换float f =(float)3.4; 或者写成 float f =3.4F;。
+
+#### short s1 = 1; s1 = s1 + 1;有错吗?short s1 = 1; s1 += 1;有错吗
+
+* 对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。
+
+* 而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。
+
+### 编码
+
+#### Java语言采用何种编码方案?有何特点?
+
+* Java语言采用Unicode编码标准,Unicode(标准码),它为每个字符制订了一个唯一的数值,因此在任何的语言,平台,程序都可以放心的使用。
+
+### 注释
+
+#### 什么Java注释
+
+**定义**:用于解释说明程序的文字
+
+**分类**
+
+* 单行注释
+ 格式: // 注释文字
+* 多行注释
+ 格式: /* 注释文字 */
+* 文档注释
+ 格式:/** 注释文字 */
+
+**作用**
+
+* 在程序中,尤其是复杂的程序中,适当地加入注释可以增加程序的可读性,有利于程序的修改、调试和交流。注释的内容在程序编译的时候会被忽视,不会产生目标代码,注释的部分不会对程序的执行结果产生任何影响。
+
+`注意事项:多行和文档注释都不能嵌套使用。`
+
+### 访问修饰符
+
+#### 访问修饰符 public,private,protected,以及不写(默认)时的区别
+
+* **定义**:Java中,可以使用访问修饰符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
+
+* **分类**
+
+ * private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
+
+ * default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
+
+ * protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
+
+ * public : 对所有类可见。使用对象:类、接口、变量、方法
+
+**访问修饰符图**
+
+
+### 运算符
+
+#### &和&&的区别
+
+* &运算符有两种用法:(1)按位与;(2)逻辑与。
+
+* &&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true 整个表达式的值才是 true。&&之所以称为短路运算,是因为如果&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。
+
+`注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。`
+
+### 关键字
+
+#### Java 有没有 goto
+
+* goto 是 Java 中的保留字,在目前版本的 Java 中没有使用。
+
+#### final 有什么用?
+
+`用于修饰类、属性和方法;`
+
+* 被final修饰的类不可以被继承
+* 被final修饰的方法不可以被重写
+* 被final修饰的变量不可以被改变,被final修饰不可变的是变量的引用,而不是引用指向的内容,引用指向的内容是可以改变的
+
+#### final finally finalize区别
+
+* final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表 示该变量是一个常量不能被重新赋值。
+* finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块 中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
+* finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调 用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,一个对象是否可回收的 最后判断。
+
+#### this关键字的用法
+
+* this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
+
+* this的用法在java中大体可以分为3种:
+
+ * 1.普通的直接引用,this相当于是指向当前对象本身。
+
+ * 2.形参与成员名字重名,用this来区分:
+
+ public Person(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+ 复制代码
+ * 3.引用本类的构造函数
+
+ class Person{
+ private String name;
+ private int age;
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+ public Person(String name, int age) {
+ this(name);
+ this.age = age;
+ }
+ }
+ 复制代码
+
+#### super关键字的用法
+
+* super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
+
+* super也有三种用法:
+
+ * 1.普通的直接引用
+
+ 与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。
+
+ * 2.子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
+
+ class Person{
+ protected String name;
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ }
+
+ class Student extends Person{
+ private String name;
+
+ public Student(String name, String name1) {
+ super(name);
+ this.name = name1;
+ }
+
+ public void getInfo(){
+ System.out.println(this.name); //Child
+ System.out.println(super.name); //Father
+ }
+
+ }
+
+ public class Test {
+ public static void main(String[] args) {
+ Student s1 = new Student("Father","Child");
+ s1.getInfo();
+
+ }
+ }
+ 复制代码
+ * 3.引用父类构造函数
+
+ * super(参数):调用父类中的某一个构造函数(应该为构造函数中的第一条语句)。
+ * this(参数):调用本类中另一种形式的构造函数(应该为构造函数中的第一条语句)。
+
+#### this与super的区别
+
+* super: 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
+* this:它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)
+* super()和this()类似,区别是,super()在子类中调用父类的构造方法,this()在本类内调用本类的其它构造方法。
+* super()和this()均需放在构造方法内第一行。
+* 尽管可以用this调用一个构造器,但却不能调用两个。
+* this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
+* this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
+* 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。
+
+#### static存在的主要意义
+
+* static的主要意义是在于创建独立于具体对象的域变量或者方法。**以致于即使没有创建对象,也能使用属性和调用方法**!
+
+* static关键字还有一个比较关键的作用就是 **用来形成静态代码块以优化程序性能**。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
+
+* 为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
+
+#### static的独特之处
+
+* 1、被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法**不属于任何一个实例对象,而是被类的实例对象所共享**。
+
+> 怎么理解 “被类的实例对象所共享” 这句话呢?就是说,一个类的静态成员,它是属于大伙的【大伙指的是这个类的多个对象实例,我们都知道一个类可以创建多个实例!】,所有的类对象共享的,不像成员变量是自个的【自个指的是这个类的单个实例对象】…我觉得我已经讲的很通俗了,你明白了咩?
+
+* 2、在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化,注意这是第一次用就要初始化,后面根据需要是可以再次赋值的。
+
+* 3、static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的!
+
+* 4、被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问。
+
+#### static应用场景
+
+* 因为static是被类的实例对象所共享,因此如果**某个成员变量是被所有对象所共享的,那么这个成员变量就应该定义为静态变量**。
+
+* 因此比较常见的static应用场景有:
+
+> 1、修饰成员变量 2、修饰成员方法 3、静态代码块 4、修饰类【只能修饰内部类也就是静态内部类】 5、静态导包
+
+#### static注意事项
+
+* 1、静态只能访问静态。
+* 2、非静态既可以访问非静态的,也可以访问静态的。
+
+### 流程控制语句
+
+#### break ,continue ,return 的区别及作用
+
+* break 跳出总上一层循环,不再执行循环(结束当前的循环体)
+
+* continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
+
+* return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)
+
+#### 在 Java 中,如何跳出当前的多重嵌套循环
+
+* 在Java中,要想跳出多重循环,可以在外面的循环语句前定义一个标号,然后在里层循环体的代码中使用带有标号的break 语句,即可跳出外层循环。例如:
+
+ public static void main(String[] args) {
+ ok:
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ System.out.println("i=" + i + ",j=" + j);
+ if (j == 5) {
+ break ok;
+ }
+ }
+ }
+ }
+ 复制代码
+
+## 面向对象
+
+### 面向对象概述
+
+#### 面向对象和面向过程的区别
+
+* **面向过程**:
+
+ * 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
+
+ * 缺点:没有面向对象易维护、易复用、易扩展
+
+* **面向对象**:
+
+ * 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
+
+ * 缺点:性能比面向过程低
+
+`面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。`
+
+`面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。`
+
+`面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。`
+
+### 面向对象三大特性
+
+#### 面向对象的特征有哪些方面
+
+**面向对象的特征主要有以下几个方面**:
+
+* **抽象**:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
+
+* **封装**把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
+
+* **继承**是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
+
+ * 关于继承如下 3 点请记住:
+
+ * 子类拥有父类非 private 的属性和方法。
+
+ * 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
+
+ * 子类可以用自己的方式实现父类的方法。(以后介绍)。
+
+* **多态**:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
+
+#### 什么是多态机制?Java语言是如何实现多态的?
+
+* 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
+
+* 多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
+
+**多态的实现**
+
+* Java实现多态有三个必要条件:继承、重写、向上转型。
+
+ * 继承:在多态中必须存在有继承关系的子类和父类。
+
+ * 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
+
+ * 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
+
+`只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。`
+
+`对于Java而言,它多态的实现机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。`
+
+#### 面向对象五大基本原则是什么(可选)
+
+* 单一职责原则SRP(Single Responsibility Principle)
+ 类的功能要单一,不能包罗万象,跟杂货铺似的。
+* 开放封闭原则OCP(Open-Close Principle)
+ 一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
+* 里式替换原则LSP(the Liskov Substitution Principle LSP)
+ 子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
+* 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
+ 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
+* 接口分离原则ISP(the Interface Segregation Principle ISP)
+ 设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。
+
+### 类与接口
+
+#### 抽象类和接口的对比
+
+* 抽象类是用来捕捉子类的通用特性的。接口是抽象方法的集合。
+
+* 从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
+
+**相同点**
+
+* 接口和抽象类都不能实例化
+* 都位于继承的顶端,用于被其他实现或继承
+* 都包含抽象方法,其子类都必须覆写这些抽象方法
+
+**不同点**
+
+| 参数 | 抽象类 | 接口 |
+| --- | --- | --- |
+| 声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
+| 实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现 | 子类使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
+| 构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
+| 访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是public。并且不允许定义为 private 或者 protected |
+| 多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
+| 字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
+
+**备注**:Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。
+
+`现在,我们可以为接口提供默认实现的方法了,并且不用强制子类来实现它。`
+
+* 接口和抽象类各有优缺点,在接口和抽象类的选择上,必须遵守这样一个原则:
+ * 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。
+ * 选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
+
+#### 普通类和抽象类有哪些区别?
+
+* 普通类不能包含抽象方法,抽象类可以包含抽象方法。
+* 抽象类不能直接实例化,普通类可以直接实例化。
+
+#### 抽象类能使用 final 修饰吗?
+
+* 不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
+
+#### 创建一个对象用什么关键字?对象实例与对象引用有何不同?
+
+* new关键字,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)
+
+### 变量与方法
+
+#### 成员变量与局部变量的区别有哪些
+
+* 变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域
+
+* 成员变量:方法外部,类内部定义的变量
+
+* 局部变量:类的方法中的变量。
+
+* 成员变量和局部变量的区别
+
+**作用域**
+
+* 成员变量:针对整个类有效。
+* 局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
+
+**存储位置**
+
+* 成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
+* 局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
+
+**生命周期**
+
+* 成员变量:随着对象的创建而存在,随着对象的消失而消失
+* 局部变量:当方法调用完,或者语句结束后,就自动释放。
+
+**初始值**
+
+* 成员变量:有默认初始值。
+* 局部变量:没有默认初始值,使用前必须赋值。
+
+#### 在Java中定义一个不做事且没有参数的构造方法的作用
+
+* Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用super()来调用父类中特定的构造方法,则编译时将发生错误,因为Java程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。
+
+#### 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
+
+* 帮助子类做初始化工作。
+
+#### 一个类的构造方法的作用是什么?若一个类没有声明构造方法,改程序能正确执行吗?为什么?
+
+* 主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。
+
+#### 构造方法有哪些特性?
+
+* 名字与类名相同;
+
+* 没有返回值,但不能用void声明构造函数;
+
+* 生成类的对象时自动执行,无需调用。
+
+#### 静态变量和实例变量区别
+
+* 静态变量: 静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
+
+* 实例变量: 每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
+
+#### 静态变量与普通变量区别
+
+* static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
+
+* 还有一点就是static成员变量的初始化顺序按照定义的顺序进行初始化。
+
+#### 静态方法和实例方法有何不同?
+
+`静态方法和实例方法的区别主要体现在两个方面:`
+
+* 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
+
+* 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
+
+#### 在一个静态方法内调用一个非静态成员为什么是非法的?
+
+* 由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
+
+#### 什么是方法的返回值?返回值的作用是什么?
+
+* 方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作!
+
+### 内部类
+
+#### 什么是内部类?
+
+* 在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是**内部类**。内部类本身就是类的一个属性,与其他属性定义方式一致。
+
+#### 内部类的分类有哪些
+
+`内部类可以分为四种:**成员内部类、局部内部类、匿名内部类和静态内部类**。`
+
+##### 静态内部类
+
+* 定义在类内部的静态类,就是静态内部类。
+
+ public class Outer {
+
+ private static int radius = 1;
+
+ static class StaticInner {
+ public void visit() {
+ System.out.println("visit outer static variable:" + radius);
+ }
+ }
+ }
+ 复制代码
+* 静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;静态内部类的创建方式,`new 外部类.静态内部类()`,如下:
+
+ Outer.StaticInner inner = new Outer.StaticInner();
+ inner.visit();
+ 复制代码
+
+##### 成员内部类
+
+* 定义在类内部,成员位置上的非静态类,就是成员内部类。
+
+ public class Outer {
+
+ private static int radius = 1;
+ private int count =2;
+
+ class Inner {
+ public void visit() {
+ System.out.println("visit outer static variable:" + radius);
+ System.out.println("visit outer variable:" + count);
+ }
+ }
+ }
+ 复制代码
+* 成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式`外部类实例.new 内部类()`,如下:
+
+ Outer outer = new Outer();
+ Outer.Inner inner = outer.new Inner();
+ inner.visit();
+ 复制代码
+
+##### 局部内部类
+
+* 定义在方法中的内部类,就是局部内部类。
+
+ public class Outer {
+
+ private int out_a = 1;
+ private static int STATIC_b = 2;
+
+ public void testFunctionClass(){
+ int inner_c =3;
+ class Inner {
+ private void fun(){
+ System.out.println(out_a);
+ System.out.println(STATIC_b);
+ System.out.println(inner_c);
+ }
+ }
+ Inner inner = new Inner();
+ inner.fun();
+ }
+ public static void testStaticFunctionClass(){
+ int d =3;
+ class Inner {
+ private void fun(){
+ // System.out.println(out_a); 编译错误,定义在静态方法中的局部类不可以访问外部类的实例变量
+ System.out.println(STATIC_b);
+ System.out.println(d);
+ }
+ }
+ Inner inner = new Inner();
+ inner.fun();
+ }
+ }
+ 复制代码
+* 定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。局部内部类的创建方式,在对应方法内,`new 内部类()`,如下:
+
+ public static void testStaticFunctionClass(){
+ class Inner {
+ }
+ Inner inner = new Inner();
+ }
+ 复制代码
+
+##### 匿名内部类
+
+* 匿名内部类就是没有名字的内部类,日常开发中使用的比较多。
+
+ public class Outer {
+
+ private void test(final int i) {
+ new Service() {
+ public void method() {
+ for (int j = 0; j < i; j++) {
+ System.out.println("匿名内部类" );
+ }
+ }
+ }.method();
+ }
+ }
+ //匿名内部类必须继承或实现一个已有的接口
+ interface Service{
+ void method();
+ }
+ 复制代码
+* 除了没有名字,匿名内部类还有以下特点:
+
+ * 匿名内部类必须继承一个抽象类或者实现一个接口。
+ * 匿名内部类不能定义任何静态成员和静态方法。
+ * 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
+ * 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
+* 匿名内部类创建方式:
+
+ new 类/接口{
+ //匿名内部类实现部分
+ }
+ 复制代码
+
+#### 内部类的优点
+
+`我们为什么要使用内部类呢?因为它有以下优点:`
+
+* 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
+* 内部类不为同一包的其他类所见,具有很好的封装性;
+* 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
+* 匿名内部类可以很方便的定义回调。
+
+#### 内部类有哪些应用场景
+
+1. 一些多算法场合
+2. 解决一些非面向对象的语句块。
+3. 适当使用内部类,使得代码更加灵活和富有扩展性。
+4. 当某个类除了它的外部类,不再被其他的类使用时。
+
+#### 局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
+
+* 局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?先看这段代码:
+
+ public class Outer {
+
+ void outMethod(){
+ final int a =10;
+ class Inner {
+ void innerMethod(){
+ System.out.println(a);
+ }
+ }
+ }
+ }
+ 复制代码
+* 以上例子,为什么要加final呢?是因为**生命周期不一致**, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
+
+#### 内部类相关,看程序说出运行结果
+
+public class Outer {
+ private int age = 12;
+
+ class Inner {
+ private int age = 13;
+ public void print() {
+ int age = 14;
+ System.out.println("局部变量:" + age);
+ System.out.println("内部类变量:" + this.age);
+ System.out.println("外部类变量:" + Outer.this.age);
+ }
+ }
+
+ public static void main(String[] args) {
+ Outer.Inner in = new Outer().new Inner();
+ in.print();
+ }
+
+}
+复制代码
+
+运行结果:
+
+局部变量:14
+内部类变量:13
+外部类变量:12
+复制代码
+### 重写与重载
+
+#### 构造器(constructor)是否可被重写(override)
+
+* 构造器不能被继承,因此不能被重写,但可以被重载。
+
+#### 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
+
+* 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
+
+* 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
+
+* 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
+
+### 对象相等判断
+
+#### == 和 equals 的区别是什么
+
+* **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)
+
+* **equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
+
+ * 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
+
+ * 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
+
+ * **举个例子:**
+
+ public class test1 {
+ public static void main(String[] args) {
+ String a = new String("ab"); // a 为一个引用
+ String b = new String("ab"); // b为另一个引用,对象的内容一样
+ String aa = "ab"; // 放在常量池中
+ String bb = "ab"; // 从常量池中查找
+ if (aa == bb) // true
+ System.out.println("aa==bb");
+ if (a == b) // false,非同一对象
+ System.out.println("a==b");
+ if (a.equals(b)) // true
+ System.out.println("aEQb");
+ if (42 == 42.0) { // true
+ System.out.println("true");
+ }
+ }
+ }
+ 复制代码
+* **说明:**
+
+ * String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
+ * 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
+
+#### hashCode 与 equals (重要)
+
+* HashSet如何检查重复
+
+* 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
+
+* hashCode和equals方法的关系
+
+* 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
+
+**hashCode()介绍**
+
+* hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
+
+* 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
+
+**为什么要有 hashCode**
+
+`我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:`
+
+* 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
+
+**hashCode()与equals()的相关规定**
+
+* 如果两个对象相等,则hashcode一定也是相同的
+
+* 两个对象相等,对两个对象分别调用equals方法都返回true
+
+* 两个对象有相同的hashcode值,它们也不一定是相等的
+
+`因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖`
+
+`hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)`
+
+#### 对象的相等与指向他们的引用相等,两者有什么不同?
+
+* 对象的相等 比的是内存中存放的内容是否相等而 引用相等 比较的是他们指向的内存地址是否相等。
+
+### 值传递
+
+#### 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递
+
+* 是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的
+
+#### 为什么 Java 中只有值传递
+
+* 首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。
+
+* **Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
+
+ * **下面通过 3 个例子来给大家说明**
+
+##### example 1
+
+public static void main(String[] args) {
+ int num1 = 10;
+ int num2 = 20;
+
+ swap(num1, num2);
+
+ System.out.println("num1 = " + num1);
+ System.out.println("num2 = " + num2);
+}
+
+public static void swap(int a, int b) {
+ int temp = a;
+ a = b;
+ b = temp;
+
+ System.out.println("a = " + a);
+ System.out.println("b = " + b);
+}
+复制代码
+
+* 结果:
+
+ a = 20 b = 10 num1 = 10 num2 = 20
+
+* 解析:
+
+
+
+* 在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
+
+`通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example.`
+
+##### example 2
+
+ public static void main(String[] args) {
+ int[] arr = { 1, 2, 3, 4, 5 };
+ System.out.println(arr[0]);
+ change(arr);
+ System.out.println(arr[0]);
+ }
+
+ public static void change(int[] array) {
+ // 将数组的第一个元素变为0
+ array[0] = 0;
+ }
+复制代码
+
+* 结果:
+
+ 1 0
+
+* 解析:
+
+
+
+* array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
+
+`通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。`
+
+`很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。`
+
+##### example 3
+
+public class Test {
+
+ public static void main(String[] args) {
+ // TODO Auto-generated method stub
+ Student s1 = new Student("小张");
+ Student s2 = new Student("小李");
+ Test.swap(s1, s2);
+ System.out.println("s1:" + s1.getName());
+ System.out.println("s2:" + s2.getName());
+ }
+
+ public static void swap(Student x, Student y) {
+ Student temp = x;
+ x = y;
+ y = temp;
+ System.out.println("x:" + x.getName());
+ System.out.println("y:" + y.getName());
+ }
+}
+复制代码
+
+* 结果:
+
+ x:小李 y:小张 s1:小张 s2:小李
+
+* 解析:
+
+* 交换之前:
+
+
+
+* 交换之后:
+
+
+
+* 通过上面两张图可以很清晰的看出:`方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝`
+
+* 总结
+
+ * `Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。`
+* 下面再总结一下Java中方法参数的使用情况:
+
+ * 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》
+ * 一个方法可以改变一个对象参数的状态。
+ * 一个方法不能让对象参数引用一个新的对象。
+
+#### 值传递和引用传递有什么区别
+
+* 值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
+
+* 引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
+
+### Java包
+
+#### JDK 中常用的包有哪些
+
+* java.lang:这个是系统的基础类;
+* java.io:这里面是所有输入输出有关的类,比如文件操作等;
+* java.nio:为了完善 io 包中的功能,提高 io 包中性能而写的一个新包;
+* java.net:这里面是与网络有关的类;
+* java.util:这个是系统辅助类,特别是集合类;
+* java.sql:这个是数据库操作的类。
+
+#### import java和javax有什么区别
+
+* 刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。
+
+`所以,实际上java和javax没有区别。这都是一个名字。`
+
+## IO流
+
+### java 中 IO 流分为几种?
+
+* 按照流的流向分,可以分为输入流和输出流;
+* 按照操作单元划分,可以划分为字节流和字符流;
+* 按照流的角色划分为节点流和处理流。
+
+`Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。`
+
+* InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
+* OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
+
+`按操作方式分类结构图:`
+
+
+
+`按操作对象分类结构图:`
+
+
+### BIO,NIO,AIO 有什么区别?
+
+* 简答
+
+ * BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
+ * NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
+ * AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
+* 详细回答
+
+ * **BIO (Blocking I/O):** 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
+ * **NIO (New I/O):** NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
+ * **AIO (Asynchronous I/O):** AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
+
+### Files的常用方法都有哪些?
+
+* Files. exists():检测文件路径是否存在。
+* Files. createFile():创建文件。
+* Files. createDirectory():创建文件夹。
+* Files. delete():删除一个文件或目录。
+* Files. copy():复制文件。
+* Files. move():移动文件。
+* Files. size():查看文件个数。
+* Files. read():读取文件。
+* Files. write():写入文件。
+
+## 反射
+
+### 什么是反射机制?
+
+* JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
+
+* 静态编译和动态编译
+
+ * 静态编译:在编译时确定类型,绑定对象
+
+ * 动态编译:运行时确定类型,绑定对象
+
+### 反射机制优缺点
+
+* **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。
+* **缺点:** 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
+
+### 反射机制的应用场景有哪些?
+
+* 反射是框架设计的灵魂。
+
+* 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
+
+* 举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
+
+### Java获取反射的三种方法
+
+1.通过new对象实现反射机制 2.通过路径实现反射机制 3.通过类名实现反射机制
+
+public class Student {
+ private int id;
+ String name;
+ protected boolean sex;
+ public float score;
+}
+
+public class Get {
+ //获取反射机制三种方式
+ public static void main(String[] args) throws ClassNotFoundException {
+ //方式一(通过建立对象)
+ Student stu = new Student();
+ Class classobj1 = stu.getClass();
+ System.out.println(classobj1.getName());
+ //方式二(所在通过路径-相对路径)
+ Class classobj2 = Class.forName("fanshe.Student");
+ System.out.println(classobj2.getName());
+ //方式三(通过类名)
+ Class classobj3 = Student.class;
+ System.out.println(classobj3.getName());
+ }
+}
+复制代码
+## 常用API
+
+### String相关
+
+#### 字符型常量和字符串常量的区别
+
+1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符
+2. 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
+3. 占内存大小 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志)
+
+#### 什么是字符串常量池?
+
+* 字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
+
+#### String 是最基本的数据类型吗
+
+* 不是。Java 中的基本数据类型只有 8 个 :byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(referencetype),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。
+
+`这是很基础的东西,但是很多初学者却容易忽视,Java 的 8 种基本数据类型中不包括 String,基本数据类型中用来描述文本数据的是 char,但是它只能表示单个字符,比如 ‘a’,‘好’ 之类的,如果要描述一段文本,就需要用多个 char 类型的变量,也就是一个 char 类型数组,比如“你好” 就是长度为2的数组 char\[\] chars = {‘你’,‘好’};`
+
+`但是使用数组过于麻烦,所以就有了 String,String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。`
+
+#### String有哪些特性
+
+* 不变性:String 是只读字符串,是一个典型的 immutable 对象,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
+
+* 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
+
+* final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。
+
+#### String为什么是不可变的吗?
+
+* 简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以:
+
+ /** The value is used for character storage. */ private final char value[];
+
+#### String真的是不可变的吗?
+
+* 我觉得如果别人问这个问题的话,回答不可变就可以了。 下面只是给大家看两个有代表性的例子:
+
+**1 String不可变但不代表引用不可以变**
+
+String str = "Hello";
+str = str + " World";
+System.out.println("str=" + str);
+复制代码
+
+* 结果:
+
+ str=Hello World
+
+* 解析:
+
+* 实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
+
+**2.通过反射是可以修改所谓的“不可变”对象**
+
+// 创建字符串"Hello World", 并赋给引用s
+String s = "Hello World";
+
+System.out.println("s = " + s); // Hello World
+
+// 获取String类中的value字段
+Field valueFieldOfString = String.class.getDeclaredField("value");
+
+// 改变value属性的访问权限
+valueFieldOfString.setAccessible(true);
+
+// 获取s对象上的value属性的值
+char[] value = (char[]) valueFieldOfString.get(s);
+
+// 改变value所引用的数组中的第5个字符
+value[5] = '_';
+
+System.out.println("s = " + s); // Hello_World
+复制代码
+
+* 结果:
+
+ s = Hello World s = Hello_World
+
+* 解析:
+
+* 用反射可以访问私有成员, 然后反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
+
+#### 是否可以继承 String 类
+
+* String 类是 final 类,不可以被继承。
+
+#### String str="i"与 String str=new String(“i”)一样吗?
+
+* 不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
+
+#### String s = new String(“xyz”);创建了几个字符串对象
+
+* 两个对象,一个是静态区的"xyz",一个是用new创建在堆上的对象。
+
+ String str1 = "hello"; //str1指向静态区 String str2 = new String("hello"); //str2指向堆上的对象 String str3 = "hello"; String str4 = new String("hello"); System.out.println(str1.equals(str2)); //true System.out.println(str2.equals(str4)); //true System.out.println(str1 == str3); //true System.out.println(str1 == str2); //false System.out.println(str2 == str4); //false System.out.println(str2 == "hello"); //false str2 = str1; System.out.println(str2 == "hello"); //true
+
+#### 如何将字符串反转?
+
+* 使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
+
+* 示例代码:
+
+ // StringBuffer reverse StringBuffer stringBuffer = new StringBuffer(); stringBuffer. append("abcdefg"); System. out. println(stringBuffer. reverse()); // gfedcba // StringBuilder reverse StringBuilder stringBuilder = new StringBuilder(); stringBuilder. append("abcdefg"); System. out. println(stringBuilder. reverse()); // gfedcba
+
+#### 数组有没有 length()方法?String 有没有 length()方法
+
+* 数组没有 length()方法 ,有 length 的属性。String 有 length()方法。JavaScript中,获得字符串的长度是通过 length 属性得到的,这一点容易和 Java 混淆。
+
+#### String 类的常用方法都有那些?
+
+* indexOf():返回指定字符的索引。
+* charAt():返回指定索引处的字符。
+* replace():字符串替换。
+* trim():去除字符串两端空白。
+* split():分割字符串,返回一个分割后的字符串数组。
+* getBytes():返回字符串的 byte 类型数组。
+* length():返回字符串长度。
+* toLowerCase():将字符串转成小写字母。
+* toUpperCase():将字符串转成大写字符。
+* substring():截取字符串。
+* equals():字符串比较。
+
+#### 在使用 HashMap 的时候,用 String 做 key 有什么好处?
+
+* HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
+
+#### String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的
+
+**可变性**
+
+* String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
+
+**线程安全性**
+
+* String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
+
+**性能**
+
+* 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
+
+**对于三者使用的总结**
+
+* 如果要操作少量的数据用 = String
+
+* 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
+
+* 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
+
+### Date相关
+
+### 包装类相关
+
+#### 自动装箱与拆箱
+
+* **装箱**:将基本类型用它们对应的引用类型包装起来;
+
+* **拆箱**:将包装类型转换为基本数据类型;
+
+#### int 和 Integer 有什么区别
+
+* Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
+
+* Java 为每个原始类型提供了包装类型:
+
+ * 原始类型: boolean,char,byte,short,int,long,float,double
+
+ * 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
+
+#### Integer a= 127 与 Integer b = 127相等吗
+
+* 对于对象引用类型:==比较的是对象的内存地址。
+* 对于基本数据类型:==比较的是值。
+
+`如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false`
+
+public static void main(String[] args) {
+ Integer a = new Integer(3);
+ Integer b = 3; // 将3自动装箱成Integer类型
+ int c = 3;
+ System.out.println(a == b); // false 两个引用没有引用同一对象
+ System.out.println(a == c); // true a自动拆箱成int类型再和c比较
+ System.out.println(b == c); // true
+
+ Integer a1 = 128;
+ Integer b1 = 128;
+ System.out.println(a1 == b1); // false
+
+ Integer a2 = 127;
+ Integer b2 = 127;
+ System.out.println(a2 == b2); // true
+}
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904127059738631
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\271\266\345\217\221.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\271\266\345\217\221.md"
new file mode 100644
index 0000000..50d9a6a
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\271\266\345\217\221.md"
@@ -0,0 +1,1478 @@
+https://juejin.cn/post/6844904119338024974#heading-17
+
+## 基础知识
+
+#### 为什么要使用并发编程
+
+* 提升多核CPU的利用率:一般来说一台主机上的会有多个CPU核心,我们可以创建多个线程,理论上讲操作系统可以将多个线程分配给不同的CPU去执行,每个CPU执行一个线程,这样就提高了CPU的使用效率,如果使用单线程就只能有一个CPU核心被使用。
+
+* 比如当我们在网上购物时,为了提升响应速度,需要拆分,减库存,生成订单等等这些操作,就可以进行拆分利用多线程的技术完成。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。
+
+* 简单来说就是:
+
+ * 充分利用多核CPU的计算能力;
+ * 方便进行业务拆分,提升应用性能
+
+#### 多线程应用场景
+
+* 例如: 迅雷多线程下载、数据库连接池、分批发送短信等。
+
+#### 并发编程有什么缺点
+
+* 并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、线程安全、死锁等问题。
+
+#### 并发编程三个必要因素是什么?
+
+* 原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
+* 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
+* 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
+
+#### 在 Java 程序中怎么保证多线程的运行安全?
+
+* 出现线程安全问题的原因一般都是三个原因:
+
+ * 线程切换带来的原子性问题 解决办法:使用多线程之间同步synchronized或使用锁(lock)。
+
+ * 缓存导致的可见性问题 解决办法:synchronized、volatile、LOCK,可以解决可见性问题
+
+ * 编译优化带来的有序性问题 解决办法:Happens-Before 规则可以解决有序性问题
+
+#### 并行和并发有什么区别?
+
+* 并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
+* 并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的“同时进行”。
+* 串行:有n个任务,由一个线程按顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题。
+
+**做一个形象的比喻:**
+
+* 并发 = 俩个人用一台电脑。
+
+* 并行 = 俩个人分配了俩台电脑。
+
+* 串行 = 俩个人排队使用一台电脑。
+
+#### 什么是多线程
+
+* 多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。
+
+#### 多线程的好处
+
+* 可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
+
+#### 多线程的劣势:
+
+* 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
+
+* 多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
+
+* 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
+
+#### 线程和进程区别
+
+* 什么是线程和进程?
+
+ * 进程
+
+ 一个在内存中运行的应用程序。 每个正在系统上运行的程序都是一个进程
+
+ * 线程
+
+ 进程中的一个执行任务(控制单元), 它负责在程序里独立执行。
+
+`一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。`
+
+* 进程与线程的区别
+
+ * 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
+
+ * 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
+
+ * 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
+
+ * 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程与进程之间的地址空间和资源是相互独立的
+
+ * 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃有可能导致整个进程都死掉。所以多进程要比多线程健壮。
+
+ * 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
+
+#### 什么是上下文切换?
+
+* 多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
+
+* 概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
+
+* 上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
+
+* Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
+
+#### 守护线程和用户线程有什么区别呢?
+
+* 用户 (User) 线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
+* 守护 (Daemon) 线程:运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作
+
+#### 如何在 Windows 和 Linux 上查找哪个线程cpu利用率最高?
+
+* windows上面用任务管理器看,linux下可以用 top 这个工具看。
+ * 找出cpu耗用厉害的进程pid, 终端执行top命令,然后按下shift+p (shift+m是找出消耗内存最高)查找出cpu利用最厉害的pid号
+ * 根据上面第一步拿到的pid号,top -H -p pid 。然后按下shift+p,查找出cpu利用率最厉害的线程号,比如top -H -p 1328
+ * 将获取到的线程号转换成16进制,去百度转换一下就行
+ * 使用jstack工具将进程信息打印输出,jstack pid号 > /tmp/t.dat,比如jstack 31365 > /tmp/t.dat
+ * 编辑/tmp/t.dat文件,查找线程号对应的信息
+
+`或者直接使用JDK自带的工具查看“jconsole” 、“visualVm”,这都是JDK自带的,可以直接在JDK的bin目录下找到直接使用`
+
+#### 什么是线程死锁
+
+* 死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
+* 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
+* 如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
+
+
+#### 形成死锁的四个必要条件是什么
+
+* 互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,就只能等待,直至占有资源的进程用毕释放。
+* 占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
+* 不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
+* 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在等B,B在等C,C在等A)
+
+#### 如何避免线程死锁
+
+1. 避免一个线程同时获得多个锁
+2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
+3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
+
+#### 创建线程的四种方式
+
+* 继承 Thread 类;
+
+```java
+public class MyThread extends Thread {
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
+ }
+```
+* 实现 Runnable 接口;
+
+```java
+public class MyRunnable implements Runnable {
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
+ }
+```
+* 实现 Callable 接口;
+
+```java
+public class MyCallable implements Callable {
+ @Override
+ public Integer call() {
+ System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
+ return 1;
+ }
+```
+* 使用匿名内部类方式
+
+```java
+public class CreateRunnable {
+ public static void main(String[] args) {
+ //创建多线程创建开始
+ Thread thread = new Thread(new Runnable() {
+ public void run() {
+ for (int i = 0; i < 10; i++) {
+ System.out.println("i:" + i);
+ }
+ }
+ });
+ thread.start();
+ }
+ }
+```
+
+#### 说一下 runnable 和 callable 有什么区别
+
+**相同点:**
+
+* 都是接口
+* 都可以编写多线程程序
+* 都采用Thread.start()启动线程
+
+**主要区别:**
+
+* Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
+* Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
+
+#### 线程的 run()和 start()有什么区别?
+
+* 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,run()方法称为线程体。通过调用Thread类的start()方法来启动一个线程。
+
+* start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。
+
+* start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。
+
+* run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
+
+#### 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
+
+这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
+
+* new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到`时间片`后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
+
+* 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
+
+总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。
+
+#### 什么是 Callable 和 Future?
+
+* Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
+
+* Future 接口表示异步任务,是一个可能还没有完成的异步任务的结果。所以说 Callable用于产生结果,Future 用于获取结果。
+
+#### 什么是 FutureTask
+
+* FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包装,由于 FutureTask 也是Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。
+
+#### 线程的状态
+
+
+
+* 新建(new):新创建了一个线程对象。
+
+* 就绪(可运行状态)(runnable):线程对象创建后,当调用线程对象的 start()方法,该线程处于就绪状态,等待被线程调度选中,获取cpu的使用权。
+
+* 运行(running):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
+
+* 阻塞(block):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。
+
+ * 阻塞的情况分三种:
+ * (一). 等待阻塞:运行状态中的线程执行 wait()方法,JVM会把该线程放入等待队列(waitting queue)中,使本线程进入到等待阻塞状态;
+ * (二). 同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步阻塞状态;
+ * (三). 其他阻塞: 通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
+* 死亡(dead)(结束):线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
+
+#### Java 中用到的线程调度算法是什么?
+
+* 计算机通常只有一个 CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU 的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权。(Java是由JVM中的线程计数器来实现线程调度)
+
+* 有两种调度模型:分时调度模型和抢占式调度模型。
+
+ * 分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解。
+
+ * Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。
+
+#### 线程的调度策略
+
+`线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:`
+
+* (1)线程体中调用了 yield 方法让出了对 cpu 的占用权利
+
+* (2)线程体中调用了 sleep 方法使线程进入睡眠状态
+
+* (3)线程由于 IO 操作受到阻塞
+
+* (4)另外一个更高优先级线程出现
+
+* (5)在支持时间片的系统中,该线程的时间片用完
+
+#### 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?
+
+* 线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。
+
+* 时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
+
+* 线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
+
+#### 请说出与线程同步以及线程调度相关的方法。
+
+* (1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
+
+* (2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
+
+* (3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
+
+* (4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
+
+#### sleep() 和 wait() 有什么区别?
+
+`两者都可以暂停线程的执行`
+
+* 类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
+* 是否释放锁:sleep() 不释放锁;wait() 释放锁。
+* 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
+* 用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
+
+#### 你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
+
+* 处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。
+
+* wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
+
+synchronized (monitor) {
+ // 判断条件谓词是否得到满足
+ while(!locked) {
+ // 等待唤醒
+ monitor.wait();
+ }
+ // 处理其他的业务逻辑
+}
+复制代码
+#### 为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
+
+* 因为Java所有类的都继承了Object,Java想让任何对象都可以作为锁,并且 wait(),notify()等方法用于等待对象的锁或者唤醒线程,在 Java 的线程中并没有可供任何对象使用的锁,所以任意对象调用方法一定定义在Object类中。
+
+* 有的人会说,既然是线程放弃对象锁,那也可以把wait()定义在Thread类里面啊,新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。然而,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。
+
+#### 为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?
+
+* 当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
+
+#### Thread 类中的 yield 方法有什么作用?
+
+* 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
+
+* 当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了。
+
+#### 为什么 Thread 类的 sleep()和 yield ()方法是静态的?
+
+* Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
+
+#### 线程的 sleep()方法和 yield()方法有什么区别?
+
+* (1) sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
+
+* (2) 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
+
+* (3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
+
+* (4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
+
+#### 如何停止一个正在运行的线程?
+
+* 在java中有以下3种方法可以终止正在运行的线程:
+ * 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
+ * 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
+ * 使用interrupt方法中断线程。
+
+#### Java 中 interrupted 和 isInterrupted 方法的区别?
+
+* interrupt:用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
+
+ 注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
+
+* interrupted:是静态方法,查看当前中断信号是true还是false并且清除中断信号。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。
+
+* isInterrupted:是可以返回当前中断信号是true还是false,与interrupt最大的差别
+
+#### 什么是阻塞式方法?
+
+* 阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket 的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
+
+#### Java 中你怎样唤醒一个阻塞的线程?
+
+* 首先 ,wait()、notify() 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取该对象的锁,直到获取成功才能往下执行;
+
+* 其次,wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。
+
+#### notify() 和 notifyAll() 有什么区别?
+
+* 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
+
+* notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。
+
+* notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
+
+#### 如何在两个线程间共享数据?
+
+* 在两个线程间共享变量即可实现共享。
+
+`一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。`
+
+#### Java 如何实现多线程之间的通讯和协作?
+
+* 可以通过中断 和 共享变量的方式实现线程间的通讯和协作
+
+* 比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。
+
+* Java中线程通信协作的最常见方式:
+
+ * 一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
+
+ * 二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
+
+* 线程间直接的数据交换:
+
+ * 三.通过管道进行线程间通信:字节流、字符流
+
+#### 同步方法和同步块,哪个是更好的选择?
+
+* 同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。
+
+* 同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。
+
+`请知道一条原则:同步的范围越小越好。`
+
+#### 什么是线程同步和线程互斥,有哪几种实现方式?
+
+* 当一个线程对共享的数据进行操作时,应使之成为一个”原子操作“,即在没有完成相关操作之前,不允许其他线程打断它,否则,就会破坏数据的完整性,必然会得到错误的处理结果,这就是线程的同步。
+
+* 在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间同时等待对方释放资源的时候就会形成线程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。
+
+* 线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
+
+* 线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
+
+* 用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。
+
+* 实现线程同步的方法
+
+ * 同步代码方法:sychronized 关键字修饰的方法
+
+ * 同步代码块:sychronized 关键字修饰的代码块
+
+ * 使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
+
+ * 使用重入锁实现线程同步:reentrantlock类是可冲入、互斥、实现了lock接口的锁他与sychronized方法具有相同的基本行为和语义
+
+#### 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
+
+* 在 java 虚拟机中,监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。
+
+* 一旦方法或者代码块被 synchronized 修饰,那么这个部分就放入了监视器的监视区域,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码
+
+* 另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案
+
+#### 如果你提交任务时,线程池队列已满,这时会发生什么
+
+* 有俩种可能:
+
+ (1)如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
+
+ (2)如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据maximumPoolSize 的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
+
+#### 什么叫线程安全?servlet 是线程安全吗?
+
+* 线程安全是编程中的术语,指某个方法在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
+
+* Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。
+
+* Struts2 的 action 是多实例多线程的,是线程安全的,每个请求过来都会 new 一个新的 action 分配给这个请求,请求完成后销毁。
+
+* SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 类似的处理流程。
+
+* Struts2 好处是不用考虑线程安全问题;Servlet 和 SpringMVC 需要考虑线程安全问题,但是性能可以提升不用处理太多的 gc,可以使用 ThreadLocal 来处理多线程的问题。
+
+#### 在 Java 程序中怎么保证多线程的运行安全?
+
+* 方法一:使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
+
+* 方法二:使用自动锁 synchronized。
+
+* 方法三:使用手动锁 Lock。
+
+* 手动锁 Java 示例代码如下:
+
+```java
+Lock lock = new ReentrantLock();
+ lock. lock();
+ try {
+ System. out. println("获得锁");
+ } catch (Exception e) {
+ // TODO: handle exception
+ } finally {
+ System. out. println("释放锁");
+ lock. unlock();
+ }
+```
+
+#### 你对线程优先级的理解是什么?
+
+* 每一个线程都是有优先级的,一般来说,高优先级的线程在运行时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个 int 变量(从 1-10),1 代表最低优先级,10 代表最高优先级。
+
+* Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如非特别需要,一般无需设置线程优先级。
+
+* 当然,如果你真的想设置优先级可以通过setPriority()方法设置,但是设置了不一定会该变,这个是不准确的
+
+#### 线程类的构造方法、静态块是被哪个线程调用的
+
+* 这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被 new这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。
+
+* 如果说上面的说法让你感到困惑,那么我举个例子,假设 Thread2 中 new 了Thread1,main 函数中 new 了 Thread2,那么:
+
+(1)Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方法是Thread2 自己调用的
+
+(2)Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run()方法是Thread1 自己调用的
+
+#### Java 中怎么获取一份线程 dump 文件?你如何在 Java 中获取线程堆栈?
+
+* Dump文件是进程的内存镜像。可以把程序的执行状态通过调试器保存到dump文件中。
+
+* 在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java应用的 dump 文件。
+
+* 在 Windows 下,你可以按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。
+
+#### 一个线程运行时发生异常会怎样?
+
+* 如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候,JVM 会使用 Thread.getUncaughtExceptionHandler()来查询线程的 UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler 的 uncaughtException()方法进行处理。
+
+#### Java 线程数过多会造成什么异常?
+
+* 线程的生命周期开销非常高
+
+* 消耗过多的 CPU
+
+ 资源如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争 CPU资源时还将产生其他性能的开销。
+
+* 降低稳定性JVM
+
+ 在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError 异常。
+
+#### 多线程的常用方法
+
+| 方法 名 | 描述 |
+| --- | --- |
+| sleep() | 强迫一个线程睡眠N毫秒 |
+| isAlive() | 判断一个线程是否存活。 |
+| join() | 等待线程终止。 |
+| activeCount() | 程序中活跃的线程数。 |
+| enumerate() | 枚举程序中的线程。 |
+| currentThread() | 得到当前线程。 |
+| isDaemon() | 一个线程是否为守护线程。 |
+| setDaemon() | 设置一个线程为守护线程。 |
+| setName() | 为线程设置一个名称。 |
+| wait() | 强迫一个线程等待。 |
+| notify() | 通知一个线程继续运行。 |
+| setPriority() | 设置一个线程的优先级。 |
+
+## 并发理论
+
+#### Java中垃圾回收有什么目的?什么时候进行垃圾回收?
+
+* 垃圾回收是在内存中存在没有引用的对象或超过作用域的对象时进行的。
+
+* 垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。
+
+#### 线程之间如何通信及线程之间如何同步
+
+* 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步。通信是指线程之间以如何来交换信息。一般线程之间的通信机制有两种:共享内存和消息传递。
+
+* Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。
+
+#### Java内存模型
+
+* 共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
+
+
+
+* 从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
+ 1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
+ 2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
+
+**下面通过示意图来说明线程之间的通信**
+
+
+
+* 总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
+
+#### 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
+
+* 不会,在下一个垃圾回调周期中,这个对象将是被可回收的。
+
+* 也就是说并不会立即被垃圾收集器立刻回收,而是在下一次垃圾回收时才会释放其占用的内存。
+
+#### finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
+
+* 1.垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法; finalize是Object类的一个方法,该方法在Object类中的声明protected void finalize() throws Throwable { } 在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其资源的回收。注意:一旦垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,并且下一次垃圾回收动作发生时,才真正回收对象占用的内存空间
+
+* 1. GC本来就是内存回收了,应用还需要在finalization做什么呢? 答案是大部分时候,什么都不用做(也就是不需要重载)。只有在某些很特殊的情况下,比如你调用了一些native的方法(一般是C写的),可以要在finaliztion里去调用C的释放函数。
+ * Finalizetion主要用来释放被对象占用的资源(不是指内存,而是指其他资源,比如文件(File Handle)、端口(ports)、数据库连接(DB Connection)等)。然而,它不能真正有效地工作。
+
+#### 什么是重排序
+
+* 程序执行的顺序按照代码的先后顺序执行。
+* 一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,进行重新排序(重排序),它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
+
+int a = 5; //语句1
+int r = 3; //语句2
+a = a + 2; //语句3
+r = a*a; //语句4
+复制代码
+
+* 则因为重排序,他还可能执行顺序为(这里标注的是语句的执行顺序) 2-1-3-4,1-3-2-4 但绝不可能 2-1-4-3,因为这打破了依赖关系。
+* 显然重排序对单线程运行是不会有任何问题,但是多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。
+
+#### 重排序实际执行的指令步骤
+
+
+
+1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
+2. 指令级并行的重排序。现代处理器采用了指令级并行技术(ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
+3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
+
+* 这些重排序对于单线程没问题,但是多线程都可能会导致多线程程序出现内存可见性问题。
+
+#### 重排序遵守的规则
+
+* as-if-serial:
+ 1. 不管怎么排序,结果不能改变
+ 2. 不存在数据依赖的可以被编译器和处理器重排序
+ 3. 一个操作依赖两个操作,这两个操作如果不存在依赖可以重排序
+ 4. 单线程根据此规则不会有问题,但是重排序后多线程会有问题
+
+#### as-if-serial规则和happens-before规则的区别
+
+* as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
+
+* as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序来执行的。
+
+* as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽可能地提高程序执行的并行度。
+
+#### 并发关键字 synchronized ?
+
+* 在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境下,控制 synchronized 代码段不被多个线程同时执行。synchronized 可以修饰类、方法、变量。
+
+* 另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
+
+#### 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗
+
+**synchronized关键字最主要的三种使用方式:**
+
+* 修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
+* 修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
+* 修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
+
+`总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!`
+
+#### 单例模式了解吗?给我解释一下双重检验锁方式实现单例模式!”
+
+**双重校验锁实现对象单例(线程安全)**
+
+**说明:**
+
+* 双锁机制的出现是为了解决前面同步问题和性能问题,看下面的代码,简单分析下确实是解决了多线程并行进来不会出现重复new对象,而且也实现了懒加载
+
+```java
+ public class Singleton {
+ private volatile static Singleton uniqueInstance;
+ private Singleton() {}
+
+ public static Singleton getUniqueInstance() {
+ //先判断对象是否已经实例过,没有实例化过才进入加锁代码
+ if (uniqueInstance == null) {
+ //类对象加锁
+ synchronized (Singleton.class) {
+ if (uniqueInstance == null) {
+ uniqueInstance = new Singleton();
+ }
+ }
+ }
+ return uniqueInstance;
+ }
+ }
+```
+`另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。`
+
+* uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:
+
+1. 为 uniqueInstance 分配内存空间
+2. 初始化 uniqueInstance
+3. 将 uniqueInstance 指向分配的内存地址
+
+`但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。`
+
+`使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。`
+
+#### 说一下 synchronized 底层实现原理?
+
+* Synchronized的语义底层是通过一个monitor(监视器锁)的对象来完成,
+
+* 每个对象有一个监视器锁(monitor)。每个Synchronized修饰过的代码当它的monitor被占用时就会处于锁定状态并且尝试获取monitor的所有权 ,过程:
+
+ 1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
+
+ 2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
+
+ 3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
+
+`synchronized是可以通过 反汇编指令 javap命令,查看相应的字节码文件。`
+
+#### synchronized可重入的原理
+
+* 重入锁是指一个线程获取到该锁之后,该线程可以继续获得该锁。底层原理维护一个计数器,当线程获取该锁时,计数器加一,再次获得该锁时继续加一,释放锁时,计数器减一,当计数器值为0时,表明该锁未被任何线程所持有,其它线程可以竞争获取锁。
+
+#### 什么是自旋
+
+* 很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
+* 忙循环:就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
+
+#### 多线程中 synchronized 锁升级的原理是什么?
+
+* synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
+
+`锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。`
+
+* 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作(比如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
+
+* 轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,轻量级锁就会升级为重量级锁;
+
+* 重量级锁是synchronized ,是 Java 虚拟机中最为基础的锁实现。在这种状态下,Java 虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。
+
+#### 线程 B 怎么知道线程 A 修改了变量
+
+* (1)volatile 修饰变量
+
+* (2)synchronized 修饰修改变量的方法
+
+* (3)wait/notify
+
+* (4)while 轮询
+
+#### 当一个线程进入一个对象的 synchronized 方法 A 之后,其它线程是否可进入此对象的 synchronized 方法 B?
+
+* 不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的 synchronized 修饰符要求执行方法时要获得对象的锁,如果已经进入A 方法说明对象锁已经被取走,那么试图进入 B 方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。
+
+#### synchronized、volatile、CAS 比较
+
+* (1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
+
+* (2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。
+
+* (3)CAS 是基于冲突检测的乐观锁(非阻塞)
+
+#### synchronized 和 Lock 有什么区别?
+
+* 首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
+* synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
+* synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
+* 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
+
+#### synchronized 和 ReentrantLock 区别是什么?
+
+* synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量
+
+* synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。
+
+* 相同点:两者都是可重入锁
+
+ 两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
+
+* 主要区别如下:
+
+ * ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
+ * ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
+ * ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。
+ * 二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark word
+* Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
+
+ * 普通同步方法,锁是当前实例对象
+ * 静态同步方法,锁是当前类的class对象
+ * 同步方法块,锁是括号里面的对象
+
+#### volatile 关键字的作用
+
+* 对于可见性,Java 提供了 volatile 关键字来保证可见性和禁止指令重排。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主内存中,当有其他线程需要读取时,它会去内存中读取新值。
+
+* 从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。
+
+* volatile 常用于多线程环境下的单次操作(单次读或者单次写)。
+
+#### Java 中能创建 volatile 数组吗?
+
+* 能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。
+
+#### volatile 变量和 atomic 变量有什么不同?
+
+* volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量,那么 count++ 操作就不是原子性的。
+
+* 而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。
+
+#### volatile 能使得一个非原子操作变成原子操作吗?
+
+* 关键字volatile的主要作用是使变量在多个线程间可见,但无法保证原子性,对于多个线程访问同一个实例变量需要加锁进行同步。
+
+* 虽然volatile只能保证可见性不能保证原子性,但用volatile修饰long和double可以保证其操作原子性。
+
+**所以从Oracle Java Spec里面可以看到:**
+
+* 对于64位的long和double,如果没有被volatile修饰,那么对其操作可以不是原子的。在操作的时候,可以分成两步,每次对32位操作。
+* 如果使用volatile修饰long和double,那么其读写都是原子操作
+* 对于64位的引用地址的读写,都是原子操作
+* 在实现JVM时,可以自由选择是否把读写long和double作为原子操作
+* 推荐JVM实现为原子操作
+
+#### synchronized 和 volatile 的区别是什么?
+
+* synchronized 表示只有一个线程可以获取作用对象的锁,执行代码,阻塞其他线程。
+
+* volatile 表示变量在 CPU 的寄存器中是不确定的,必须从主存中读取。保证多线程环境下变量的可见性;禁止指令重排序。
+
+**区别**
+
+* volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
+
+* volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
+
+* volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
+
+* volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
+
+* volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
+
+#### final不可变对象,它对写并发应用有什么帮助?
+
+* 不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据,也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)。
+
+* 不可变对象的类即为不可变类(Immutable Class)。Java 平台类库中包含许多不可变类,如 String、基本类型的包装类、BigInteger 和 BigDecimal 等。
+
+* 只有满足如下状态,一个对象才是不可变的;
+
+ * 它的状态不能在创建后再被修改;
+
+ * 所有域都是 final 类型;并且,它被正确创建(创建期间没有发生 this 引用的逸出)。
+
+`不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。`
+
+#### Lock 接口和synchronized 对比同步它有什么优势?
+
+* Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
+
+* 它的优势有:
+
+ * (1)可以使锁更公平
+
+ * (2)可以使线程在等待锁的时候响应中断
+
+ * (3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
+
+ * (4)可以在不同的范围,以不同的顺序获取和释放锁
+
+* 整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。
+
+#### 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
+
+* 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。
+
+* 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
+
+#### 什么是 CAS
+
+* CAS 是 compare and swap 的缩写,即我们所说的比较交换。
+
+* cas 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。
+
+* CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。
+
+`java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的(AtomicInteger,AtomicBoolean,AtomicLong)`
+
+#### CAS 的会产生什么问题?
+
+* 1、ABA 问题:
+
+ 比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 one 的 CAS 操作成功,但可能存在潜藏的问题。从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
+
+* 2、循环时间长开销大:
+
+ 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
+
+* 3、只能保证一个共享变量的原子操作:
+
+ 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。
+
+#### 什么是原子类
+
+* java.util.concurrent.atomic包:是原子类的小工具包,支持在单个变量上解除锁的线程安全编程 原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。
+
+* 比如:AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。
+
+`简单来说就是原子类来实现CAS无锁模式的算法`
+
+#### 原子类的常用类
+
+* AtomicBoolean
+* AtomicInteger
+* AtomicLong
+* AtomicReference
+
+#### 说一下 Atomic的原理?
+
+* Atomic包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操作时,具有排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等到执行成功。
+
+#### 死锁与活锁的区别,死锁与饥饿的区别?
+
+* 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
+
+* 活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。
+
+* 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,这就是所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
+
+* 饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
+
+ Java 中导致饥饿的原因:
+
+ * 1、高优先级线程吞噬所有的低优先级线程的 CPU 时间。
+
+ * 2、线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
+
+ * 3、线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。
+
+## 线程池
+
+#### 什么是线程池?
+
+* Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来许多好处。
+ * 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
+ * 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
+ * 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用
+
+#### 线程池作用?
+
+* 线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
+
+* 如果一个线程所需要执行的时间非常长的话,就没必要用线程池了(不是不能作长时间操作,而是不宜。本来降低线程创建和销毁,结果你那么久我还不好控制还不如直接创建线程),况且我们还不能控制线程池中线程的开始、挂起、和中止。
+
+#### 线程池有什么优点?
+
+* 降低资源消耗:重用存在的线程,减少对象创建销毁的开销。
+
+* 提高响应速度。可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
+
+* 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
+
+* 附加功能:提供定时执行、定期执行、单线程、并发数控制等功能。
+
+#### 什么是ThreadPoolExecutor?
+
+* **ThreadPoolExecutor就是线程池**
+
+ ThreadPoolExecutor其实也是JAVA的一个类,我们一般通过Executors工厂类的方法,通过传入不同的参数,就可以构造出适用于不同应用场景下的ThreadPoolExecutor(线程池)
+
+构造参数图:
+
+`构造参数参数介绍:`corePoolSize 核心线程数量
+maximumPoolSize 最大线程数量
+keepAliveTime 线程保持时间,N个时间单位
+unit 时间单位(比如秒,分)
+workQueue 阻塞队列
+threadFactory 线程工厂
+handler 线程池拒绝策略
+复制代码
+#### 什么是Executors?
+
+* **Executors框架实现的就是线程池的功能。**
+
+ Executors工厂类中提供的newCachedThreadPool、newFixedThreadPool 、newScheduledThreadPool 、newSingleThreadExecutor 等方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,
+
+Executor工厂类如何创建线程池图:
+
+
+#### 线程池四种创建方式?
+
+* **Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:**
+ 1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
+ 2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
+ 3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
+ 4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
+
+#### 在 Java 中 Executor 和 Executors 的区别?
+
+* Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
+
+* Executor 接口对象能执行我们的线程任务。
+
+* ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
+
+* 使用 ThreadPoolExecutor 可以创建自定义线程池。
+
+#### 四种构建线程池的区别及特点?
+
+##### 1\. newCachedThreadPool
+
+* **特点**:newCachedThreadPool创建一个可缓存线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的长度作任何限制
+
+* **缺点**:他虽然可以无线的新建线程,但是容易造成堆外内存溢出,因为它的最大值是在初始化的时候设置为 Integer.MAX_VALUE,一般来说机器都没那么大内存给它不断使用。当然知道可能出问题的点,就可以去重写一个方法限制一下这个最大值
+
+* **总结**:线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+
+ public class TestNewCachedThreadPool {
+ public static void main(String[] args) {
+ // 创建无限大小线程池,由jvm自动回收
+ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
+ for (int i = 0; i < 10; i++) {
+ final int temp = i;
+ newCachedThreadPool.execute(new Runnable() {
+ public void run() {
+ try {
+ Thread.sleep(100);
+ } catch (Exception e) {
+ }
+ System.out.println(Thread.currentThread().getName() + ",i==" + temp);
+ }
+ });
+ }
+ }
+ }
+
+```
+
+##### 2.newFixedThreadPool
+
+* **特点**:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。
+
+* **缺点**:线程数量是固定的,但是阻塞队列是无界队列。如果有很多请求积压,阻塞队列越来越长,容易导致OOM(超出内存空间)
+
+* **总结**:请求的挤压一定要和分配的线程池大小匹配,定线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
+
+`Runtime.getRuntime().availableProcessors()方法是查看电脑CPU核心数量)`
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+
+ public class TestNewFixedThreadPool {
+ public static void main(String[] args) {
+ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
+ for (int i = 0; i < 10; i++) {
+ final int temp = i;
+ newFixedThreadPool.execute(new Runnable() {
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + ",i==" + temp);
+ }
+ });
+ }
+ }
+ }
+
+```
+
+##### 3.newScheduledThreadPool
+
+* **特点**:创建一个固定长度的线程池,而且支持定时的以及周期性的任务执行,类似于Timer(Timer是Java的一个定时器类)
+
+* **缺点**:由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务(比如:一个任务出错,以后的任务都无法继续)。
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+
+ public class TestNewScheduledThreadPool {
+ public static void main(String[] args) {
+ //定义线程池大小为3
+ ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
+ for (int i = 0; i < 10; i++) {
+ final int temp = i;
+ newScheduledThreadPool.schedule(new Runnable() {
+ public void run() {
+ System.out.println("i:" + temp);
+ }
+ }, 3, TimeUnit.SECONDS);//这里表示延迟3秒执行。
+ }
+ }
+ }
+
+```
+
+##### 4.newSingleThreadExecutor
+
+* **特点**:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,他必须保证前一项任务执行完毕才能执行后一项。保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
+
+* **缺点**:缺点的话,很明显,他是单线程的,高并发业务下有点无力
+
+* **总结**:保证所有任务按照指定顺序执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它
+
+* **代码示例:**
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ExecutorService;
+ import java.util.concurrent.Executors;
+
+ public class TestNewSingleThreadExecutor {
+ public static void main(String[] args) {
+ ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
+ for (int i = 0; i < 10; i++) {
+ final int index = i;
+ newSingleThreadExecutor.execute(new Runnable() {
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " index:" + index);
+ try {
+ Thread.sleep(200);
+ } catch (Exception e) {
+ }
+ }
+ });
+ }
+ }
+ }
+
+```
+
+#### 线程池都有哪些状态?
+
+* RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
+* SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
+* STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
+* TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
+* TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
+
+#### 线程池中 submit() 和 execute() 方法有什么区别?
+
+* 相同点:
+ * 相同点就是都可以开启线程执行池中的任务。
+* 不同点:
+ * 接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。
+ * 返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
+ * 异常处理:submit()方便Exception处理
+
+#### 什么是线程组,为什么在 Java 中不推荐使用?
+
+* ThreadGroup 类,可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。
+
+* 线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。
+
+* 为什么不推荐使用线程组?因为使用有很多的安全隐患吧,没有具体追究,如果需要使用,推荐使用线程池。
+
+#### ThreadPoolExecutor饱和策略有哪些?
+
+`如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,ThreadPoolTaskExecutor 定义一些策略:`
+
+* ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
+* ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
+* ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
+* ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
+
+#### 如何自定义线程线程池?
+
+* 先看ThreadPoolExecutor(线程池)这个类的构造参数
+
+ 构造参数参数介绍:
+```java
+ corePoolSize 核心线程数量
+ maximumPoolSize 最大线程数量
+ keepAliveTime 线程保持时间,N个时间单位
+ unit 时间单位(比如秒,分)
+ workQueue 阻塞队列
+ threadFactory 线程工厂
+ handler 线程池拒绝策略
+
+```
+* 代码示例:
+
+```java
+package com.lijie;
+
+ import java.util.concurrent.ArrayBlockingQueue;
+ import java.util.concurrent.ThreadPoolExecutor;
+ import java.util.concurrent.TimeUnit;
+
+ public class Test001 {
+ public static void main(String[] args) {
+ //创建线程池
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
+ for (int i = 1; i <= 6; i++) {
+ TaskThred t1 = new TaskThred("任务" + i);
+ //executor.execute(t1);是执行线程方法
+ executor.execute(t1);
+ }
+ //executor.shutdown()不再接受新的任务,并且等待之前提交的任务都执行完再关闭,阻塞队列中的任务不会再执行。
+ executor.shutdown();
+ }
+ }
+
+ class TaskThred implements Runnable {
+ private String taskName;
+
+ public TaskThred(String taskName) {
+ this.taskName = taskName;
+ }
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + taskName);
+ }
+ }
+
+```
+
+#### 线程池的执行原理?
+
+
+
+* 提交一个任务到线程池中,线程池的处理流程如下:
+
+ 1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
+
+ 2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
+
+ 3. 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
+
+#### 如何合理分配线程池大小?
+
+* 要合理的分配线程池的大小要根据实际情况来定,简单的来说的话就是根据CPU密集和IO密集来分配
+
+##### 什么是CPU密集
+
+* CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
+
+* CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那样。
+
+##### 什么是IO密集
+
+* IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
+
+##### 分配CPU和IO密集:
+
+1. CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
+
+2. IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
+
+##### 精确来说的话的话:
+
+* 从以下几个角度分析任务的特性:
+
+ * 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
+
+ * 任务的优先级:高、中、低。
+
+ * 任务的执行时间:长、中、短。
+
+ * 任务的依赖性:是否依赖其他系统资源,如数据库连接等。
+
+**可以得出一个结论:**
+
+* 线程等待时间比CPU执行时间比例越高,需要越多线程。
+* 线程CPU执行时间比等待时间比例越高,需要越少线程。
+
+## 并发容器
+
+#### 你经常使用什么并发容器,为什么?
+
+* 答:Vector、ConcurrentHashMap、HasTable
+
+* 一般软件开发中容器用的最多的就是HashMap、ArrayList,LinkedList ,等等
+
+* 但是在多线程开发中就不能乱用容器,如果使用了未加锁(非同步)的的集合,你的数据就会非常的混乱。由此在多线程开发中需要使用的容器必须是加锁(同步)的容器。
+
+#### 什么是Vector
+
+* Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,访问它比访问ArrayList慢很多
+
+ (`ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。ArrayList的缺点是每个元素之间不能有间隔。`)
+
+#### ArrayList和Vector有什么不同之处?
+
+* Vector方法带上了synchronized关键字,是线程同步的
+
+1. ArrayList添加方法源码
+2. Vector添加源码(加锁了synchronized关键字)
+
+#### 为什么HashTable是线程安全的?
+
+* 因为HasTable的内部方法都被synchronized修饰了,所以是线程安全的。其他的都和HashMap一样
+
+1. HashMap添加方法的源码
+2. HashTable添加方法的源码
+
+#### 用过ConcurrentHashMap,讲一下他和HashTable的不同之处?
+
+* ConcurrentHashMap是Java5中支持高并发、高吞吐量的线程安全HashMap实现。它由Segment数组结构和HashEntry数组结构组成。Segment数组在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键-值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构;一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素;每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
+
+* 看不懂???很正常,我也看不懂
+
+* 总结:
+
+ 1. HashTable就是实现了HashMap加上了synchronized,而ConcurrentHashMap底层采用分段的数组+链表实现,线程安全
+ 2. ConcurrentHashMap通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
+ 3. 并且读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。
+ 4. Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
+ 5. 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
+
+#### Collections.synchronized * 是什么?
+
+`注意:* 号代表后面是还有内容的`
+
+* 此方法是干什么的呢,他完完全全的可以把List、Map、Set接口底下的集合变成线程安全的集合
+
+* Collections.synchronized * :原理是什么,我猜的话是代理模式:[Java代理模式理解](https://link.juejin.cn?target=https%3A%2F%2Fblog.csdn.net%2Fweixin_43122090%2Farticle%2Fdetails%2F104883274 "https://blog.csdn.net/weixin_43122090/article/details/104883274")
+
+
+#### Java 中 ConcurrentHashMap 的并发度是什么?
+
+* ConcurrentHashMap 把实际 map 划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是 ConcurrentHashMap 类构造函数的一个可选参数,默认值为 16,这样在多线程情况下就能避免争用。
+
+* 在 JDK8 后,它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法。同时加入了更多的辅助变量来提高并发度,具体内容还是查看源码吧。
+
+#### 什么是并发容器的实现?
+
+* 何为同步容器:可以简单地理解为通过 synchronized 来实现同步的容器,如果有多个线程调用同步容器的方法,它们将会串行执行。比如 Vector,Hashtable,以及 Collections.synchronizedSet,synchronizedList 等方法返回的容器。可以通过查看 Vector,Hashtable 等这些同步容器的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字 synchronized。
+
+* 并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,例如在 ConcurrentHashMap 中采用了一种粒度更细的加锁机制,可以称为分段锁,在这种锁机制下,允许任意数量的读线程并发地访问 map,并且执行读操作的线程和写操作的线程也可以并发的访问 map,同时允许一定数量的写操作线程并发地修改 map,所以它可以在并发环境下实现更高的吞吐量。
+
+#### Java 中的同步集合与并发集合有什么区别?
+
+* 同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在 Java1.5 之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性。Java5 介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。
+
+#### SynchronizedMap 和 ConcurrentHashMap 有什么区别?
+
+* SynchronizedMap 一次锁住整张表来保证线程安全,所以每次只能有一个线程来访为 map。
+
+* ConcurrentHashMap 使用分段锁来保证在多线程下的性能。
+
+* ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默认将hash 表分为 16 个桶,诸如 get,put,remove 等常用操作只锁当前需要用到的桶。
+
+* 这样,原来只能一个线程进入,现在却能同时有 16 个写线程执行,并发性能的提升是显而易见的。
+
+* 另外 ConcurrentHashMap 使用了一种不同的迭代方式。在这种迭代方式中,当iterator 被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时 new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据 ,这样 iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。
+
+#### CopyOnWriteArrayList 是什么?
+
+* CopyOnWriteArrayList 是一个并发容器。有很多人称它是线程安全的,我认为这句话不严谨,缺少一个前提条件,那就是非复合场景下操作它是线程安全的。
+
+* CopyOnWriteArrayList(免锁容器)的好处之一是当多个迭代器同时遍历和修改这个列表时,不会抛出 ConcurrentModificationException。在CopyOnWriteArrayList 中,写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。
+
+#### CopyOnWriteArrayList 的使用场景?
+
+* 合适读多写少的场景。
+
+#### CopyOnWriteArrayList 的缺点?
+
+* 由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致 young gc 或者 full gc。
+* 不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set 操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求。
+* 由于实际使用中可能没法保证 CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次 add/set 都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。
+
+#### CopyOnWriteArrayList 的设计思想?
+
+* 读写分离,读和写分开
+* 最终一致性
+* 使用另外开辟空间的思路,来解决并发冲突
+
+## 并发队列
+
+#### 什么是并发队列:
+
+* 消息队列很多人知道:消息队列是分布式系统中重要的组件,是系统与系统直接的通信
+
+* 并发队列是什么:并发队列多个线程以有次序共享数据的重要组件
+
+#### 并发队列和并发集合的区别:
+
+`那就有可能要说了,我们并发集合不是也可以实现多线程之间的数据共享吗,其实也是有区别的:`
+
+* 队列遵循“先进先出”的规则,可以想象成排队检票,队列一般用来解决大数据量采集处理和显示的。
+
+* 并发集合就是在多个线程中共享数据的
+
+#### 怎么判断并发队列是阻塞队列还是非阻塞队列
+
+* 在并发队列上JDK提供了Queue接口,一个是以Queue接口下的BlockingQueue接口为代表的阻塞队列,另一个是高性能(无堵塞)队列。
+
+#### 阻塞队列和非阻塞队列区别
+
+* 当队列阻塞队列为空的时,从队列中获取元素的操作将会被阻塞。
+
+* 或者当阻塞队列是满时,往队列里添加元素的操作会被阻塞。
+
+* 或者试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
+
+* 试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来
+
+#### 常用并发列队的介绍:
+
+1. **非堵塞队列:**
+
+ 1. **ArrayDeque, (数组双端队列)**
+
+ ArrayDeque (非堵塞队列)是JDK容器中的一个双端队列实现,内部使用数组进行元素存储,不允许存储null值,可以高效的进行元素查找和尾部插入取出,是用作队列、双端队列、栈的绝佳选择,性能比LinkedList还要好。
+
+ 2. **PriorityQueue, (优先级队列)**
+
+ PriorityQueue (非堵塞队列) 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象
+
+ 3. **ConcurrentLinkedQueue, (基于链表的并发队列)**
+
+ ConcurrentLinkedQueue (非堵塞队列): 是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。ConcurrentLinkedQueue的性能要好于BlockingQueue接口,它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。该队列不允许null元素。
+
+2. **堵塞队列:**
+
+ 1. **DelayQueue, (基于时间优先级的队列,延期阻塞队列)**
+
+ DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。
+
+ 2. **ArrayBlockingQueue, (基于数组的并发阻塞队列)**
+
+ ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据
+
+ 3. **LinkedBlockingQueue, (基于链表的FIFO阻塞队列)**
+
+ LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。
+
+ 4. **LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列)**
+
+ LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。
+
+ 相比于其他阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法,以first结尾的方法,表示插入、获取获移除双端队列的第一个元素。以last结尾的方法,表示插入、获取获移除双端队列的最后一个元素。
+
+ LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。
+
+ 5. **PriorityBlockingQueue, (带优先级的无界阻塞队列)**
+
+ priorityBlockingQueue是一个无界队列,它没有限制,在内存允许的情况下可以无限添加元素;它又是具有优先级的队列,是通过构造函数传入的对象来判断,传入的对象必须实现comparable接口。
+
+ 6. **SynchronousQueue (并发同步阻塞队列)**
+
+ SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
+
+ 将这个类称为队列有点夸大其词。这更像是一个点。
+
+#### 并发队列的常用方法
+
+`不管是那种列队,是那个类,当是他们使用的方法都是差不多的`
+
+| 方法名 | 描述 |
+| --- | --- |
+| add() | 在不超出队列长度的情况下插入元素,可以立即执行,成功返回true,如果队列满了就抛出异常。 |
+| offer() | 在不超出队列长度的情况下插入元素的时候则可以立即在队列的尾部插入指定元素,成功时返回true,如果此队列已满,则返回false。 |
+| put() | 插入元素的时候,如果队列满了就进行等待,直到队列可用。 |
+| take() | 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。 |
+| poll(long timeout, TimeUnit unit) | 在给定的时间里,从队列中获取值,如果没有取到会抛出异常。 |
+| remainingCapacity() | 获取队列中剩余的空间。 |
+| remove(Object o) | 从队列中移除指定的值。 |
+| contains(Object o) | 判断队列中是否拥有该值。 |
+| drainTo(Collection c) | 将队列中值,全部移除,并发设置到给定的集合中。 |
+
+## 并发工具类
+
+#### 常用的并发工具类有哪些?
+
+* CountDownLatch
+
+ CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
+
+* CyclicBarrier (回环栅栏) CyclicBarrier它的作用就是会让所有线程都等待完成后才会继续下一步行动。
+
+ CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
+
+ CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。
+
+* Semaphore (信号量) Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量(允许自定义多少线程同时访问)。就这一点而言,单纯的synchronized 关键字是实现不了的。
+
+ Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。它的用法如下:
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904125755293710
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\274\202\345\270\270.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\274\202\345\270\270.md"
new file mode 100644
index 0000000..566eeaa
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\345\274\202\345\270\270.md"
@@ -0,0 +1,739 @@
+
+
+## Java异常架构与异常关键字
+
+### Java异常简介
+
+* Java异常是Java提供的一种识别及响应错误的一致性机制。
+ Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。
+
+### Java异常架构
+
+
+#### 1\. Throwable
+
+* Throwable 是 Java 语言中所有错误与异常的超类。
+
+* Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
+
+* Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
+
+#### 2\. Error(错误)
+
+* **定义**:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
+
+* **特点**:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
+
+* 这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!
+
+#### 3\. Exception(异常)
+
+* 程序本身可以捕获并且可以处理的异常。Exception 这种异常又分为两类:运行时异常和编译时异常。
+
+##### 运行时异常
+
+* **定义**:RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。
+
+* **特点**:Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
+
+* RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获(**就算我们没写异常捕获语句运行时也会抛出错误**!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
+
+##### 编译时异常
+
+* **定义**: Exception 中除 RuntimeException 及其子类之外的异常。
+
+* **特点**: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。**该异常我们必须手动在代码里添加捕获语句来处理该异常**。
+
+#### 4\. 受检异常与非受检异常
+
+* Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。
+
+##### 受检异常
+
+* 编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。**除 RuntimeException 及其子类外,其他的 Exception 异常都属于受检异常**。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
+
+##### 非受检异常
+
+* 编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。**该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。**
+
+### Java异常关键字
+
+* **try** – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
+* **catch** – 用于捕获异常。catch用来捕获try语句块中发生的异常。
+* **finally** – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
+* **throw** – 用于抛出异常。
+* **throws** – 用在方法签名中,用于声明该方法可能抛出的异常。
+
+## Java异常处理
+
+
+
+* Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。
+
+* 在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。
+
+### 声明异常
+
+* 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 **throws** 关键字声明可能会抛出的异常。
+
+**注意**
+
+* 非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。
+* 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。
+
+### 抛出异常
+
+* 如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。
+
+* throw关键字作用是在方法内部抛出一个`Throwable`类型的异常。任何Java代码都可以通过throw语句抛出异常。
+
+### 捕获异常
+
+* 程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。
+
+### 如何选择异常类型
+
+* 可以根据下图来选择是捕获异常,声明异常还是抛出异常
+
+
+### 常见异常处理方式
+
+#### 直接抛出异常
+
+* 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 **throws** 关键字声明可能会抛出的异常。
+
+private static void readFile(String filePath) throws IOException {
+ File file = new File(filePath);
+ String result;
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ while((result = reader.readLine())!=null) {
+ System.out.println(result);
+ }
+ reader.close();
+}
+复制代码
+#### 封装异常再抛出
+
+* 有时我们会从 catch 中抛出一个异常,目的是为了改变异常的类型。多用于在多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。
+
+private static void readFile(String filePath) throws MyException {
+ try {
+ // code
+ } catch (IOException e) {
+ MyException ex = new MyException("read file failed.");
+ ex.initCause(e);
+ throw ex;
+ }
+}
+复制代码
+#### 捕获异常
+
+* 在一个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理
+
+private static void readFile(String filePath) {
+ try {
+ // code
+ } catch (FileNotFoundException e) {
+ // handle FileNotFoundException
+ } catch (IOException e){
+ // handle IOException
+ }
+}
+复制代码
+
+* 同一个 catch 也可以捕获多种类型异常,用 | 隔开
+
+private static void readFile(String filePath) {
+ try {
+ // code
+ } catch (FileNotFoundException | UnknownHostException e) {
+ // handle FileNotFoundException or UnknownHostException
+ } catch (IOException e){
+ // handle IOException
+ }
+}
+复制代码
+#### 自定义异常
+
+* 习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用)
+
+public class MyException extends Exception {
+ public MyException(){ }
+ public MyException(String msg){
+ super(msg);
+ }
+ // ...
+}
+复制代码
+#### try-catch-finally
+
+* 当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决这个问题。
+
+private static void readFile(String filePath) throws MyException {
+ File file = new File(filePath);
+ String result;
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ while((result = reader.readLine())!=null) {
+ System.out.println(result);
+ }
+ } catch (IOException e) {
+ System.out.println("readFile method catch block.");
+ MyException ex = new MyException("read file failed.");
+ ex.initCause(e);
+ throw ex;
+ } finally {
+ System.out.println("readFile method finally block.");
+ if (null != reader) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
+复制代码
+
+* 调用该方法时,读取文件时若发生异常,代码会进入 catch 代码块,之后进入 finally 代码块;若读取文件时未发生异常,则会跳过 catch 代码块直接进入 finally 代码块。所以无论代码中是否发生异常,fianlly 中的代码都会执行。
+
+* 若 catch 代码块中包含 return 语句,finally 中的代码还会执行吗?将以上代码中的 catch 子句修改如下:
+
+catch (IOException e) {
+ System.out.println("readFile method catch block.");
+ return;
+}
+复制代码
+
+* 调用 readFile 方法,观察当 catch 子句中调用 return 语句时,finally 子句是否执行
+
+readFile method catch block.
+readFile method finally block.
+复制代码
+
+* 可见,即使 catch 中包含了 return 语句,finally 子句依然会执行。若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return.
+
+#### try-with-resource
+
+* 上面例子中,finally 中的 close 方法也可能抛出 IOException, 从而覆盖了原始异常。JAVA 7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类。
+
+private static void tryWithResourceTest(){
+ try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
+ // code
+ } catch (IOException e){
+ // handle exception
+ }
+}
+复制代码
+
+* try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用 getSuppressed 方法来获取。
+
+## Java异常常见面试题
+
+### 1\. Error 和 Exception 区别是什么?
+
+* Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
+
+* Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
+
+### 2\. 运行时异常和一般异常(受检异常)区别是什么?
+
+* 运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
+
+* 受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
+
+* **RuntimeException异常和受检异常之间的区别**:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。
+
+### 3\. JVM 是如何处理异常的?
+
+* 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
+
+* JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。
+
+### 4\. throw 和 throws 的区别是什么?
+
+* Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。
+
+**throws 关键字和 throw 关键字在使用上的几点区别如下**:
+
+* throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
+* throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。
+
+### 5\. final、finally、finalize 有什么区别?
+
+* final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
+* finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
+* finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
+
+### 6\. NoClassDefFoundError 和 ClassNotFoundException 区别?
+
+* NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。
+
+* 引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;
+
+* ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。
+
+### 7\. try-catch-finally 中哪个部分可以省略?
+
+* 答:catch 可以省略
+
+**原因**
+
+* 更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
+
+* 理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。
+
+* 至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。
+
+### 8\. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
+
+* 答:会执行,在 return 前执行。
+
+* **注意**:在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误。
+
+**代码示例1:**
+
+public static int getInt() {
+ int a = 10;
+ try {
+ System.out.println(a / 0);
+ a = 20;
+ } catch (ArithmeticException e) {
+ a = 30;
+ return a;
+ /*
+ * return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
+ * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
+ * 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
+ */
+ } finally {
+ a = 40;
+ }
+ return a;
+}
+复制代码
+
+* 执行结果:30
+
+**代码示例2:**
+
+public static int getInt() {
+ int a = 10;
+ try {
+ System.out.println(a / 0);
+ a = 20;
+ } catch (ArithmeticException e) {
+ a = 30;
+ return a;
+ } finally {
+ a = 40;
+ //如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
+ return a;
+ }
+
+}
+复制代码
+
+* 执行结果:40
+
+### 9\. 类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA。
+
+* 有如下代码片断:
+
+try {
+ throw new ExampleB("b")
+} catch(ExampleA e){
+ System.out.println("ExampleA");
+} catch(Exception e){
+ System.out.println("Exception");
+}
+复制代码
+
+* 请问执行此段代码的输出是什么?
+
+* **答**:输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取 ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的异常)
+
+* 面试题 - 说出下面代码的运行结果。(此题的出处是《Java 编程思想》一书)
+
+class Annoyance extends Exception {
+}
+class Sneeze extends Annoyance {
+}
+class Human {
+ public static void main(String[] args)
+ throws Exception {
+ try {
+ try {
+ throw new Sneeze();
+ } catch ( Annoyance a ) {
+ System.out.println("Caught Annoyance");
+ throw a;
+ }
+ } catch ( Sneeze s ) {
+ System.out.println("Caught Sneeze");
+ return ;
+ } finally {
+ System.out.println("Hello World!");
+ }
+ }
+}
+复制代码
+
+* 结果
+
+Caught Annoyance
+Caught Sneeze
+Hello World!
+复制代码
+### 10\. 常见的 RuntimeException 有哪些?
+
+* ClassCastException(类转换异常)
+* IndexOutOfBoundsException(数组越界)
+* NullPointerException(空指针)
+* ArrayStoreException(数据存储异常,操作数组时类型不一致)
+* 还有IO操作的BufferOverflowException异常
+
+### 11\. Java常见异常有哪些
+
+* java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
+
+* java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
+
+* java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
+
+* java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
+
+* java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
+
+* java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
+
+* java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
+
+* java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
+
+* java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
+
+* java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
+
+* java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
+
+* java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。-
+
+* java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
+
+* java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
+
+* java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
+
+## Java异常处理最佳实践
+
+* 在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范进行异常处理的原因。而团队之间的这些规范往往是截然不同的。
+
+* 本文给出几个被很多团队使用的异常处理最佳实践。
+
+### 1\. 在 finally 块中清理资源或者使用 try-with-resource 语句
+
+* 当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。
+
+public void doNotCloseResourceInTry() {
+ FileInputStream inputStream = null;
+ try {
+ File file = new File("./tmp.txt");
+ inputStream = new FileInputStream(file);
+ // use the inputStream to read a file
+ // do NOT do this
+ inputStream.close();
+ } catch (FileNotFoundException e) {
+ log.error(e);
+ } catch (IOException e) {
+ log.error(e);
+ }
+}
+复制代码
+
+* 问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try 代码块内代码会正常执行,并且资源可以正常关闭。但是,使用 try 代码块是有原因的,一般调用一个或多个可能抛出异常的方法,而且,你自己也可能会抛出一个异常,这意味着代码可能不会执行到 try 代码块的最后部分。结果就是,你并没有关闭资源。
+
+所以,你应该把清理工作的代码放到 finally 里去,或者使用 try-with-resource 特性。
+
+#### 1.1 使用 finally 代码块
+
+* 与前面几行 try 代码块不同,finally 代码块总是会被执行。不管 try 代码块成功执行之后还是你在 catch 代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。
+
+public void closeResourceInFinally() {
+ FileInputStream inputStream = null;
+ try {
+ File file = new File("./tmp.txt");
+ inputStream = new FileInputStream(file);
+ // use the inputStream to read a file
+ } catch (FileNotFoundException e) {
+ log.error(e);
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ log.error(e);
+ }
+ }
+ }
+}
+复制代码
+#### 1.2 Java 7 的 try-with-resource 语法
+
+* 如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。
+
+public void automaticallyCloseResource() {
+ File file = new File("./tmp.txt");
+ try (FileInputStream inputStream = new FileInputStream(file);) {
+ // use the inputStream to read a file
+ } catch (FileNotFoundException e) {
+ log.error(e);
+ } catch (IOException e) {
+ log.error(e);
+ }
+}
+
+复制代码
+
+
+
+### 2\. 优先明确的异常
+
+* 你抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常。
+
+* 因此需要保证提供给他们尽可能多的信息。这样你的 API 更容易被理解。你的方法的调用者能够更好的处理异常并且避免额外的检查。
+
+* 因此,总是尝试寻找最适合你的异常事件的类,例如,抛出一个 NumberFormatException 来替换一个 IllegalArgumentException 。避免抛出一个不明确的异常。
+
+public void doNotDoThis() throws Exception {
+ ...
+}
+public void doThis() throws NumberFormatException {
+ ...
+}
+
+复制代码
+
+
+
+### 3\. 对异常进行文档说明
+
+* 当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。
+ 在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。
+
+public void doSomething(String input) throws MyBusinessException {
+ ...
+}
+
+复制代码
+### 4\. 使用描述性消息抛出异常
+
+* 在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。
+
+* 但这里并不是说要对错误信息长篇大论,因为本来 Exception 的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。
+
+* 如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是 NumberFormatException 。当你以错误的格式提供 String 时,它将被 java.lang.Long 类的构造函数抛出。
+
+try {
+ new Long("xyz");
+} catch (NumberFormatException e) {
+ log.error(e);
+}
+
+复制代码
+### 5\. 优先捕获最具体的异常
+
+* 大多数 IDE 都可以帮助你实现这个最佳实践。当你尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。
+
+* 但问题在于,只有匹配异常的第一个 catch 块会被执行。 因此,如果首先捕获 IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。
+
+* 总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。
+
+* 你可以在下面的代码片断中看到这样一个 try-catch 语句的例子。 第一个 catch 块处理所有 NumberFormatException 异常,第二个处理所有非 NumberFormatException 异常的IllegalArgumentException 异常。
+
+public void catchMostSpecificExceptionFirst() {
+ try {
+ doSomething("A message");
+ } catch (NumberFormatException e) {
+ log.error(e);
+ } catch (IllegalArgumentException e) {
+ log.error(e)
+ }
+}
+
+复制代码
+
+
+
+### 6\. 不要捕获 Throwable 类
+
+* Throwable 是所有异常和错误的超类。你可以在 catch 子句中使用它,但是你永远不应该这样做!
+
+* 如果在 catch 子句中使用 Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应用程序处理的严重问题。 典型的例子是 OutOfMemoryError 或者 StackOverflowError 。两者都是由应用程序控制之外的情况引起的,无法处理。
+
+* 所以,最好不要捕获 Throwable ,除非你确定自己处于一种特殊的情况下能够处理错误。
+
+public void doNotCatchThrowable() {
+ try {
+ // do something
+ } catch (Throwable t) {
+ // don't do this!
+ }
+}
+
+复制代码
+### 7\. 不要忽略异常
+
+* 很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。
+
+public void doNotIgnoreExceptions() {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ // this will never happen
+ }
+}
+
+复制代码
+
+* 但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。
+
+* 合理的做法是至少要记录异常的信息。
+
+public void logAnException() {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ log.error("This should never happen: " + e);
+ }
+}
+
+复制代码
+### 8\. 不要记录并抛出异常
+
+* 这可能是本文中最常被忽略的最佳实践。可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下:
+
+try {
+ new Long("xyz");
+} catch (NumberFormatException e) {
+ log.error(e);
+ throw e;
+}
+
+复制代码
+
+* 这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下:
+
+17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
+Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
+at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
+at java.lang.Long.parseLong(Long.java:589)
+at java.lang.Long.(Long.java:965)
+at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
+at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
+
+复制代码
+
+* 如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。
+
+public void wrapException(String input) throws MyBusinessException {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ throw new MyBusinessException("A message that describes the error.", e);
+ }
+}
+
+复制代码
+
+* 因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。
+
+### 9\. 包装异常时不要抛弃原始的异常
+
+* 捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。
+ 在你这样做时,请确保将原始异常设置为原因(注:参考下方代码 NumberFormatException e 中的原始异常 e )。Exception 类提供了特殊的构造函数方法,它接受一个 Throwable 作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。
+
+public void wrapException(String input) throws MyBusinessException {
+ try {
+ // do something
+ } catch (NumberFormatException e) {
+ throw new MyBusinessException("A message that describes the error.", e);
+ }
+}
+
+复制代码
+
+
+
+### 10\. 不要使用异常控制程序的流程
+
+* 不应该使用异常控制应用的执行流程,例如,本应该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯,会严重影响应用的性能。
+
+### 11\. 使用标准异常
+
+* 如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。
+
+### 12\. 异常会影响性能
+
+* 异常处理的性能成本非常高,每个 Java 程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。
+
+ * 仅在异常情况下使用异常;
+ * 在可恢复的异常情况下使用异常;
+* 尽管使用异常有利于 Java 开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。
+
+### 13\. 总结
+
+* 综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者 API 的可用性。
+
+* 异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。
+
+### 异常处理-阿里巴巴Java开发手册
+
+1. 【强制】Java 类库中定义的可以通过预检查方式规避的RuntimeException异常不应该通过catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException等等。 说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过catch NumberFormatException来实现。 正例:if (obj != null) {…} 反例:try { obj.method(); } catch (NullPointerException e) {…}
+
+2. 【强制】异常不要用来做流程控制,条件控制。 说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。
+
+3. 【强制】catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理。 说明:对大段代码进行try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。 正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。
+
+4. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
+
+5. 【强制】有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务。
+
+6. 【强制】finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。 说明:如果JDK7及以上,可以使用try-with-resources方式。
+
+7. 【强制】不要在finally块中使用return。 说明:try块中的return语句执行成功后,并不马上返回,而是继续执行finally块中的语句,如果此处存在return语句,则在此直接返回,无情丢弃掉try块中的返回点。 反例:
+
+ private int x = 0;
+ public int checkReturn() {
+ try {
+ // x等于1,此处不返回
+ return ++x;
+ } finally {
+ // 返回的结果是2
+ return ++x;
+ }
+ }
+
+复制代码
+
+1. 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。
+
+2. 【强制】在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截。 说明:通过反射机制来调用方法,如果找不到方法,抛出NoSuchMethodException。什么情况会抛出NoSuchMethodError呢?二方包在类冲突时,仲裁机制可能导致引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(比如:ASM)动态创建或修改类时,修改了相应的方法签名。这些情况,即使代码编译期是正确的,但在代码运行期时,会抛出NoSuchMethodError。
+
+3. 【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。 说明:本手册明确防止NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的情况。
+
+4. 【推荐】防止NPE,是程序员的基本修养,注意NPE产生的场景: 1) 返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE。 反例:public int f() { return Integer对象}, 如果为null,自动解箱抛NPE。 2) 数据库的查询结果可能为null。 3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null。 4) 远程调用返回对象时,一律要求进行空指针判断,防止NPE。 5) 对于Session中获取的数据,建议进行NPE检查,避免空指针。 6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE。
+ 正例:使用JDK8的Optional类来防止NPE问题。
+
+5. 【推荐】定义时区分unchecked / checked 异常,避免直接抛出new RuntimeException(),更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException等。
+
+6. 【参考】对于公司外的http/api开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess()方法、“错误码”、“错误简短信息”。 说明:关于RPC方法返回方式使用Result方式的理由: 1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 2)如果不加栈信息,只是new自定义异常,加入自己的理解的error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。
+
+7. 【参考】避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。 说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。 正例:一个类中有多个public方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
+ private boolean checkParam(DTO dto) {…}
+
+作者:小杰要吃蛋
+链接:https://juejin.cn/post/6844904128959741965
+来源:稀土掘金
+著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
\ No newline at end of file
diff --git "a/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\350\231\232\346\213\237\346\234\272.md" "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\350\231\232\346\213\237\346\234\272.md"
new file mode 100644
index 0000000..382dac4
--- /dev/null
+++ "b/docs/java/\345\237\272\347\241\200\351\235\242\350\257\225\351\242\230/Java\350\231\232\346\213\237\346\234\272.md"
@@ -0,0 +1,627 @@
+
+
+## Java内存模型
+
+### 我们开发人员编写的Java代码是怎么让电脑认识的
+
+* 首先先了解电脑是二进制的系统,他只认识 01010101
+
+* 比如我们经常要编写 HelloWord.java 电脑是怎么认识运行的
+
+* HelloWord.java是我们程序员编写的,我们人可以认识,但是电脑不认识
+
+**Java文件编译的过程**
+
+1. 程序员编写的.java文件
+2. 由javac编译成字节码文件.class:(为什么编译成class文件,因为JVM只认识.class文件)
+3. 在由JVM编译成电脑认识的文件 (对于电脑系统来说 文件代表一切)
+
+`(这是一个大概的观念 抽象画的概念)`
+
+
+### 为什么说java是跨平台语言
+
+* 这个夸平台是中间语言(JVM)实现的夸平台
+* Java有JVM从软件层面屏蔽了底层硬件、指令层面的细节让他兼容各种系统
+
+`难道 C 和 C++ 不能夸平台吗 其实也可以` `C和C++需要在编译器层面去兼容不同操作系统的不同层面,写过C和C++的就知道不同操作系统的有些代码是不一样`
+
+### Jdk和Jre和JVM的区别
+
+* Jdk包括了Jre和Jvm,Jre包括了Jvm
+
+* Jdk是我们编写代码使用的开发工具包
+
+* Jre 是Java的运行时环境,他大部分都是 C 和 C++ 语言编写的,他是我们在编译java时所需要的基础的类库
+
+* Jvm俗称Java虚拟机,他是java运行环境的一部分,它虚构出来的一台计算机,在通过在实际的计算机上仿真模拟各种计算机功能来实现Java应用程序
+
+* 看Java官方的图片,Jdk中包括了Jre,Jre中包括了JVM
+
+ 
+
+### 说一下 JVM由那些部分组成,运行流程是什么?
+
+
+
+* JVM包含两个子系统和两个组件: 两个子系统为Class loader(类装载)、Execution engine(执行引擎); 两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。
+
+ * Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。
+
+ * Execution engine(执行引擎):执行classes中的指令。
+
+ * Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。
+
+ * Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。
+
+* **流程** :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。
+
+### 说一下 JVM 运行时数据区
+
+* Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:
+
+`简单的说就是我们java运行时的东西是放在那里的`
+
+
+
+* 程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;
+
+ `为什么要线程计数器?因为线程是不具备记忆功能`
+
+* Java 虚拟机栈(Java Virtual Machine Stacks):每个方法在执行的同时都会在Java 虚拟机栈中创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
+
+ `栈帧就是Java虚拟机栈中的下一个单位`
+
+* 本地方法栈(Native Method Stack):与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;
+
+ `Native 关键字修饰的方法是看不到的,Native 方法的源码大部分都是 C和C++ 的代码`
+
+* Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;
+
+* 方法区(Methed Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。
+
+`后面有详细的说明JVM 运行时数据区`
+
+### 详细的介绍下程序计数器?(重点理解)
+
+1. 程序计数器是一块较小的内存空间,它可以看作是:保存当前线程所正在执行的字节码指令的地址(行号)
+
+2. 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。称之为“线程私有”的内存。程序计数器内存区域是虚拟机中唯一没有规定OutOfMemoryError情况的区域。
+
+ `总结:也可以把它叫做线程计数器`
+
+* **例子**:在java中最小的执行单位是线程,线程是要执行指令的,执行的指令最终操作的就是我们的电脑,就是 CPU。在CPU上面去运行,有个非常不稳定的因素,叫做调度策略,这个调度策略是时基于时间片的,也就是当前的这一纳秒是分配给那个指令的。
+
+* **假如**:
+
+ * 线程A在看直播 
+
+ * 突然,线程B来了一个视频电话,就会抢夺线程A的时间片,就会打断了线程A,线程A就会挂起 
+
+ * 然后,视频电话结束,这时线程A究竟该干什么? (线程是最小的执行单位,他不具备记忆功能,他只负责去干,那这个记忆就由:**程序计数器来记录**) 
+
+### 详细介绍下Java虚拟机栈?(重点理解)
+
+1. Java虚拟机是线程私有的,它的生命周期和线程相同。
+2. 虚拟机栈描述的是Java方法执行的内存模型:`每个方法在执行的同时`都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
+
+* **解释**:虚拟机栈中是有单位的,单位就是**栈帧**,一个方法一个**栈帧**。一个**栈帧**中他又要存储,局部变量,操作数栈,动态链接,出口等。
+
+**解析栈帧:**
+
+1. 局部变量表:是用来存储我们临时8个基本数据类型、对象引用地址、returnAddress类型。(returnAddress中保存的是return后要执行的字节码的指令地址。)
+2. 操作数栈:操作数栈就是用来操作的,例如代码中有个 i = 6*6,他在一开始的时候就会进行操作,读取我们的代码,进行计算后再放入局部变量表中去
+3. 动态链接:假如我方法中,有个 service.add()方法,要链接到别的方法中去,这就是动态链接,存储链接的地方。
+4. 出口:出口是什呢,出口正常的话就是return 不正常的话就是抛出异常落
+
+#### 一个方法调用另一个方法,会创建很多栈帧吗?
+
+* 答:会创建。如果一个栈中有动态链接调用别的方法,就会去创建新的栈帧,栈中是由顺序的,一个栈帧调用另一个栈帧,另一个栈帧就会排在调用者下面
+
+#### 栈指向堆是什么意思?
+
+* 栈指向堆是什么意思,就是栈中要使用成员变量怎么办,栈中不会存储成员变量,只会存储一个应用地址
+
+#### 递归的调用自己会创建很多栈帧吗?
+
+* 答:递归的话也会创建多个栈帧,就是在栈中一直从上往下排下去
+
+### 你能给我详细的介绍Java堆吗?(重点理解)
+
+* java堆(Java Heap)是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。
+* 在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
+* java堆是垃圾收集器管理的主要区域,因此也被成为“GC堆”。
+* 从内存回收角度来看java堆可分为:新生代和老生代。
+* 从内存分配的角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区。
+* 无论怎么划分,都与存放内容无关,无论哪个区域,存储的都是对象实例,进一步的划分都是为了更好的回收内存,或者更快的分配内存。
+* 根据Java虚拟机规范的规定,java堆可以处于物理上不连续的内存空间中。当前主流的虚拟机都是可扩展的(通过 -Xmx 和 -Xms 控制)。如果堆中没有内存可以完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
+
+### 能不能解释一下本地方法栈?
+
+1. 本地方法栈很好理解,他很栈很像,只不过方法上带了 native 关键字的栈字
+2. 它是虚拟机栈为虚拟机执行Java方法(也就是字节码)的服务方法
+3. native关键字的方法是看不到的,必须要去oracle官网去下载才可以看的到,而且native关键字修饰的大部分源码都是C和C++的代码。
+4. 同理可得,本地方法栈中就是C和C++的代码
+
+### 能不能解释一下方法区(重点理解)
+
+1. 方法区是所有线程共享的内存区域,它用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
+2. 它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
+
+### 什么是JVM字节码执行引擎
+
+* 虚拟机核心的组件就是执行引擎,它负责执行虚拟机的字节码,一般户先进行编译成机器码后执行。
+
+* “虚拟机”是一个相对于“物理机”的概念,虚拟机的字节码是不能直接在物理机上运行的,需要JVM字节码执行引擎- 编译成机器码后才可在物理机上执行。
+
+### 你听过直接内存吗?
+
+* 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现,所以我们放到这里一起讲解。
+* 我的理解就是直接内存是基于物理内存和Java虚拟机内存的中间内存
+
+### 知道垃圾收集系统吗?
+
+* 程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行自动的垃圾回收(GC)。
+
+* 垃圾收集系统是Java的核心,也是不可少的,Java有一套自己进行垃圾清理的机制,开发人员无需手工清理
+
+* 有一部分原因就是因为Java垃圾回收系统的强大导致Java领先市场
+
+### 堆栈的区别是什么?
+
+> | 对比 | JVM堆 | JVM栈 |
+> | --- | --- | --- |
+> | 物理地址 | 堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩) | 栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。 |
+> | 内存分别 | 堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。 | 栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。 |
+> | 存放的内容 | 堆存放的是对象的实例和数组。因此该区更关注的是数据的存储 | 栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。 |
+> | 程序的可见度 | 堆对于整个应用程序都是共享、可见的。 | 栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。 |
+
+* 注意:
+ * 静态变量放在方法区
+ * 静态的对象还是放在堆。
+
+### 深拷贝和浅拷贝
+
+* 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
+* 深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
+* 浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
+* 深复制:在计算机中开辟一块**新的内存地址**用于存放复制的对象。
+
+### Java会存在内存泄漏吗?请说明为什么?
+
+* 内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。
+
+* 但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,`尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收`,这就是java中内存泄露的发生场景。
+
+## 垃圾回收机制及算法
+
+### 简述Java垃圾回收机制
+
+* 在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
+
+### GC是什么?为什么要GC
+
+* GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。
+
+### 垃圾回收的优点和缺点
+
+* 优点:JVM的垃圾回收器都不需要我们手动处理无引用的对象了,这个就是最大的优点
+
+* 缺点:程序员不能实时的对某个对象或所有对象调用垃圾回收器进行垃圾回收。
+
+### 垃圾回收器的原理是什么?有什么办法手动进行垃圾回收?
+
+* 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。
+
+* 通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。
+
+* 可以。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。
+
+### JVM 中都有哪些引用类型?
+
+* 强引用:发生 gc 的时候不会被回收。
+* 软引用:有用但不是必须的对象,在发生内存溢出之前会被回收。
+* 弱引用:有用但不是必须的对象,在下一次GC时会被回收。
+* 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。
+
+### 怎么判断对象是否可以被回收?
+
+* 垃圾收集器在做垃圾回收的时候,首先需要判定的就是哪些内存是需要被回收的,哪些对象是存活的,是不可以被回收的;哪些对象已经死掉了,需要被回收。
+
+* 一般有两种方法来判断:
+
+ * 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;(这个已经淘汰了)
+ * 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。(市场上用的非常非常广泛)
+
+### Full GC是什么
+
+* 清理整个堆空间—包括年轻代和老年代和永久代
+* 因为Full GC是清理整个堆空间所以Full GC执行速度非常慢,在Java开发中最好保证少触发Full GC
+
+### 对象什么时候可以被垃圾器回收
+
+* 当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。
+* 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。
+
+### JVM 垃圾回收算法有哪些?
+
+* 标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。
+* 复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。
+* 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。
+* 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。
+
+#### 标记-清除算法
+
+* 标记无用对象,然后进行清除回收。
+
+* 标记-清除算法(Mark-Sweep)是一种常见的基础垃圾收集算法,它将垃圾收集分为两个阶段:
+
+ * 标记阶段:标记出可以回收的对象。
+ * 清除阶段:回收被标记的对象所占用的空间。
+* 标记-清除算法之所以是基础的,是因为后面讲到的垃圾收集算法都是在此算法的基础上进行改进的。
+
+* **优点**:实现简单,不需要对象进行移动。
+
+* **缺点**:标记、清除过程效率低,产生大量不连续的内存碎片,提高了垃圾回收的频率。
+
+* 标记-清除算法的执行的过程如下图所示
+
+
+#### 复制算法
+
+* 为了解决标记-清除算法的效率不高的问题,产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。
+
+* **优点**:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片。
+
+* **缺点**:可用的内存大小缩小为原来的一半,对象存活率高时会频繁进行复制。
+
+* 复制算法的执行过程如下图所示
+
+
+#### 标记-整理算法
+
+* 在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-整理算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。回收后,已用和未用的内存都各自一边。
+
+* **优点**:解决了标记-清理算法存在的内存碎片问题。
+
+* **缺点**:仍需要进行局部对象移动,一定程度上降低了效率。
+
+* 标记-整理算法的执行过程如下图所示
+
+
+#### 分代收集算法
+
+* 当前商业虚拟机都采用 `分代收集`的垃圾收集算法。分代收集算法,顾名思义是根据对象的`存活周期`将内存划分为几块。一般包括`年轻代`、`老年代`和 `永久代`,如图所示:`(后面有重点讲解)`
+
+
+### JVM中的永久代中会发生垃圾回收吗
+
+* 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区
+ (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
+
+## 垃圾收集器以及新生代、老年代、永久代
+
+### 讲一下新生代、老年代、永久代的区别
+
+
+
+* 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。而新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
+
+* 新生代中一般保存新出现的对象,所以每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了`复制算法`,只需要付出少量存活对象的复制成本就可以完成收集。
+
+* 老年代中一般保存存活了很久的对象,他们存活率高、没有额外空间对它进行分配担保,就必须采用`“标记-清理”或者“标记-整理”`算法。
+
+* 永久代就是JVM的方法区。在这里都是放着一些被虚拟机加载的类信息,静态变量,常量等数据。这个区中的东西比老年代和新生代更不容易回收。
+
+### Minor GC、Major GC、Full GC是什么
+
+1. Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。(一般采用复制算法回收垃圾)
+2. Major GC是老年代GC,指的是发生在老年代的GC,通常执行Major GC会连着Minor GC一起执行。Major GC的速度要比Minor GC慢的多。(可采用标记清楚法和标记整理法)
+3. Full GC是清理整个堆空间,包括年轻代和老年代
+
+### Minor GC、Major GC、Full GC区别及触发条件
+
+* **Minor GC 触发条件一般为:**
+
+ 1. eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。
+ 2. 新创建的对象大小 > Eden所剩空间时触发Minor GC
+* **Major GC和Full GC 触发条件一般为:** `Major GC通常是跟full GC是等价的`
+
+ 1. 每次晋升到老年代的对象平均大小>老年代剩余空间
+
+ 2. MinorGC后存活的对象超过了老年代剩余空间
+
+ 3. 永久代空间不足
+
+ 4. 执行System.gc()
+
+ 5. CMS GC异常
+
+ 6. 堆内存分配很大的对象
+
+### 为什么新生代要分Eden和两个 Survivor 区域?
+
+* 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
+* Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历15次Minor GC还能在新生代中存活的对象,才会被送到老年代。
+* 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
+
+### Java堆老年代( Old ) 和新生代 ( Young ) 的默认比例?
+
+* 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。
+
+* 其中,新生代 ( Young ) 被细分为 Eden 和 **两个 Survivor 区域**,Edem 和俩个Survivor 区域比例是 = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),
+
+* 但是JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
+
+### 为什么要这样分代:
+
+* 其实主要原因就是可以根据各个年代的特点进行对象分区存储,更便于回收,采用最适当的收集算法:
+
+ * 新生代中,每次垃圾收集时都发现大批对象死去,只有少量对象存活,便采用了复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
+
+ * 而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须采用“标记-清理”或者“标记-整理”算法。
+
+* 新生代又分为Eden和Survivor (From与To,这里简称一个区)两个区。加上老年代就这三个区。数据会首先分配到Eden区当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。当Eden没有足够空间的时候就会触发jvm发起一次Minor GC,。如果对象经过一次Minor-GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代中了,当然晋升老年代的年龄是可以设置的。
+
+### 什么是垃圾回收器他和垃圾算法有什么区别
+
+* 垃圾收集器是垃圾回收算法(标记清楚法、标记整理法、复制算法、分代算法)的具体实现,不同垃圾收集器、不同版本的JVM所提供的垃圾收集器可能会有很在差别。
+
+### 说一下 JVM 有哪些垃圾回收器?
+
+* 如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。
+
+
+
+> | 垃圾回收器 | 工作区域 | 回收算法 | 工作线程 | 用户线程并行 | 描述 |
+> | --- | --- | --- | --- | --- | --- |
+> | Serial | 新生带 | 复制算法 | 单线程 | 否 | Client模式下默认新生代收集器。简单高效 |
+> | ParNew | 新生带 | 复制算法 | 多线程 | 否 | Serial的多线程版本,Server模式下首选, 可搭配CMS的新生代收集器 |
+> | Parallel Scavenge | 新生带 | 复制算法 | 多线程 | 否 | 目标是达到可控制的吞吐量 |
+> | Serial Old | 老年带 | 标记-整理 | 单线程 | 否 | Serial老年代版本,给Client模式下的虚拟机使用 |
+> | Parallel Old | 老年带 | 标记-整理 | 多线程 | 否 | Parallel Scavenge老年代版本,吞吐量优先 |
+> | | | | | | |
+> | G1 | 新生带 + 老年带 | 标记-整理 + 复制算法 | 多线程 | 是 | JDK1.9默认垃圾收集器 |
+
+* Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
+* ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
+* Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
+* Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
+* Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
+* CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
+* G1(Garbage First)收集器 ( `标记整理 + 复制算法来回收垃圾` ): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
+
+### 收集器可以这么分配?(了解就好了)
+
+Serial / Serial Old
+Serial / CMS
+ParNew / Serial Old
+ParNew / CMS
+Parallel Scavenge / Serial Old
+Parallel Scavenge / Parallel Old
+G1
+复制代码
+### 新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?
+
+* 新生代回收器:Serial、ParNew、Parallel Scavenge
+* 老年代回收器:Serial Old、Parallel Old、CMS
+* 整堆回收器:G1
+
+新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。
+
+### 简述分代垃圾回收器是怎么工作的?
+
+* 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。
+
+* 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:
+
+ * 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
+ * 清空 Eden 和 From Survivor 分区;
+ * From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。
+* 每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。
+
+* 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
+
+## 内存分配策略
+
+### 简述java内存分配与回收策率以及Minor GC和Major GC
+
+* 所谓自动内存管理,最终要解决的也就是内存分配和内存回收两个问题。前面我们介绍了内存回收,这里我们再来聊聊内存分配。
+
+* 对象的内存分配通常是在 Java 堆上分配(随着虚拟机优化技术的诞生,某些场景下也会在栈上分配,后面会详细介绍),对象主要分配在新生代的 Eden 区,如果启动了本地线程缓冲,将按照线程优先在 TLAB 上分配。少数情况下也会直接在老年代上分配。总的来说分配规则不是百分百固定的,其细节取决于哪一种垃圾收集器组合以及虚拟机相关参数有关,但是虚拟机对于内存的分配还是会遵循以下几种「普世」规则:
+
+#### 对象优先在 Eden 区分配
+
+* 多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。
+ * 这里我们提到 Minor GC,如果你仔细观察过 GC 日常,通常我们还能从日志中发现 Major GC/Full GC。
+ * **Minor GC** 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;
+ * **Major GC/Full GC** 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。
+
+#### 为什么大对象直接进入老年代
+
+* 所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对象。
+
+* 前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。
+
+#### 长期存活对象将进入老年代
+
+* 虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15) 就会被晋升到老年代。
+
+## 虚拟机类加载机制
+
+### 简述java类加载机制?
+
+* 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。
+
+### 类加载的机制及过程
+
+* 程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
+
+
+##### 1、加载
+
+* 加载指的是将类的class文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。
+
+* Java类加载器由JVM提供,是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。
+
+* 类加载器,可以从不同来源加载类的二进制数据,比如:本地Class文件、Jar包Class文件、网络Class文件等等等。
+
+* 类加载的最终产物就是位于堆中的Class对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即Java反射的接口
+
+##### 2、连接过程
+
+* 当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中(意思就是将java类的二进制代码合并到JVM的运行状态之中)。类连接又可分为如下3个阶段。
+
+1. 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。主要验证是否符合Class文件格式规范,并且是否能被当前的虚拟机加载处理。
+
+2. 准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配
+
+3. 解析:虚拟机常量池的符号引用替换为字节引用过程
+
+##### 3、初始化
+
+* 初始化阶段是执行类构造器``() 方法的过程。类构造器`