├── docs ├── 微信.png ├── Jyannis.jpg ├── IO │ ├── docs │ │ ├── 五种IO模型.jpg │ │ ├── 异步IO模型.png │ │ ├── 阻塞IO模型.png │ │ ├── BIO基本原理.png │ │ ├── Selector.png │ │ ├── 信号驱动IO模型.png │ │ ├── 多路复用IO模型.png │ │ └── 非阻塞IO模型.png │ ├── 一文读懂BIO、NIO、AIO.md │ └── 操作系统层面的IO.md ├── redis │ ├── docs │ │ ├── 重启加载.png │ │ ├── AOF工作流程.png │ │ ├── AOF阻塞流程.png │ │ └── RDB工作流程.png │ ├── 常见缓存问题及其解决.md │ ├── 概述.md │ ├── 数据结构.md │ ├── 持久化.md │ └── 常用命令.md ├── qrcode_1586150172794.jpg ├── tech-life │ ├── 200412-简历-专业技能.png │ ├── 200412-简历-项目经历-范例1.png │ ├── 200412-简历-项目经历-范例2.png │ └── 今天我收到了蚂蚁金服实习offer.md ├── Spring │ ├── Spring AOP.md │ └── Spring IOC.md └── JVM │ └── JMM Java内存模型.md └── README.md /docs/微信.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/微信.png -------------------------------------------------------------------------------- /docs/Jyannis.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/Jyannis.jpg -------------------------------------------------------------------------------- /docs/IO/docs/五种IO模型.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/五种IO模型.jpg -------------------------------------------------------------------------------- /docs/IO/docs/异步IO模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/异步IO模型.png -------------------------------------------------------------------------------- /docs/IO/docs/阻塞IO模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/阻塞IO模型.png -------------------------------------------------------------------------------- /docs/IO/docs/BIO基本原理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/BIO基本原理.png -------------------------------------------------------------------------------- /docs/IO/docs/Selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/Selector.png -------------------------------------------------------------------------------- /docs/IO/docs/信号驱动IO模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/信号驱动IO模型.png -------------------------------------------------------------------------------- /docs/IO/docs/多路复用IO模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/多路复用IO模型.png -------------------------------------------------------------------------------- /docs/IO/docs/非阻塞IO模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/IO/docs/非阻塞IO模型.png -------------------------------------------------------------------------------- /docs/redis/docs/重启加载.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/redis/docs/重启加载.png -------------------------------------------------------------------------------- /docs/redis/docs/AOF工作流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/redis/docs/AOF工作流程.png -------------------------------------------------------------------------------- /docs/redis/docs/AOF阻塞流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/redis/docs/AOF阻塞流程.png -------------------------------------------------------------------------------- /docs/redis/docs/RDB工作流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/redis/docs/RDB工作流程.png -------------------------------------------------------------------------------- /docs/qrcode_1586150172794.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/qrcode_1586150172794.jpg -------------------------------------------------------------------------------- /docs/tech-life/200412-简历-专业技能.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/tech-life/200412-简历-专业技能.png -------------------------------------------------------------------------------- /docs/tech-life/200412-简历-项目经历-范例1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/tech-life/200412-简历-项目经历-范例1.png -------------------------------------------------------------------------------- /docs/tech-life/200412-简历-项目经历-范例2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jyannis/JavaLearning/HEAD/docs/tech-life/200412-简历-项目经历-范例2.png -------------------------------------------------------------------------------- /docs/redis/常见缓存问题及其解决.md: -------------------------------------------------------------------------------- 1 | 缓存雪崩、穿透、击穿是每个学习缓存使用的人都应该熟知的问题。 2 | 3 | **目录** 4 | 5 | - [缓存雪崩](#缓存雪崩) 6 | - [缓存穿透](#缓存穿透) 7 | - [缓存击穿](#缓存击穿) 8 | 9 |
10 | 11 |
12 | 13 |
14 | 15 | ## 缓存雪崩 16 | 17 | #### 概念 18 | 19 | 缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 20 | 21 |
22 | 23 | #### 解决方案 24 | 25 | - 在批量往**Redis**存数据的时候,把每个Key的失效时间都加个随机值 26 | - 如果**Redis**是集群部署,将热点数据均匀分布在不同的**Redis**库中也能避免全部失效的问题 27 | - 设置热点数据永远不过期,有更新操作就更新缓存 28 | - 选择合适的内存淘汰策略 29 | 30 |
31 | 32 |
33 | 34 | ## 缓存穿透 35 | 36 | #### 概念 37 | 38 | 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。 39 | 40 |
41 | 42 | #### 解决方案 43 | 44 | - 添加参数校验,权限校验,对不合法情况进行过滤 45 | - 如果有越过缓存直接访问数据库的请求,就为它添加值为null的key。但这样可能导致key过多 46 | - 利用布隆过滤器(把数据库里有的数据添加到布隆过滤器里,请求数据库前先查过滤器看看有没有) 47 | 48 |
49 | 50 |
51 | 52 | ## 缓存击穿 53 | 54 | #### 概念 55 | 56 | **缓存击穿**是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,导致数据库崩掉。 57 | 58 |
59 | 60 | #### 解决方案 61 | 62 | - 设置热点数据永不过期 63 | - 也可以考虑对访问数据的代码块加上互斥锁(性能一般不太好) -------------------------------------------------------------------------------- /docs/redis/概述.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Redis概述中包括了一些我们需要知道的Redis相关的基本概念,例如它的优点、内容、使用场景、如何开启关闭等等。 6 | 7 | **目录** 8 | 9 | - [Redis为什么速度快](#Redis为什么速度快) 10 | - [Redis有哪些数据结构](#Redis有哪些数据结构) 11 | - [Redis可以做什么(使用场景)](#Redis可以做什么) 12 | - [Redis是否支持事务](#Redis是否支持事务) 13 | - [如何启动Redis](#如何启动Redis) 14 | - [Redis的关闭过程](#Redis的关闭过程) 15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | ## Redis为什么速度快 23 | 24 | 1. 内存数据库 25 | 26 | Redis是基于内存的,而我们都知道内存访问速度远快于磁盘访问。 27 | 28 | 2. IO多路复用技术(使用epoll) 29 | 30 | **多路复用**是网络IO中非常重要的一个概念,由于篇幅有限这里就不多赘述了,希望不太了解的读者要自行好好查阅。 31 | 32 | 3. 单线程架构 33 | 34 | Redis是单线程的架构,这意味着没有多线程的上下文切换开销。 35 | 36 |
37 | 38 |
39 | 40 | ## Redis有哪些数据结构 41 | 42 | - 字符串String 43 | - 基于字符串还演变出位图Bitmaps和HyperLogLog 44 | - 哈希Hash 45 | - 列表List 46 | - 集合Set 47 | - 有序集合Zset 48 | 49 |
50 | 51 |
52 | 53 | ## Redis可以做什么 54 | 55 | 1. 一般缓存 56 | 57 | 使用字符串String 58 | 59 | 2. 排行榜 60 | 61 | 使用有序集合Zset 62 | 63 | 3. 计数器 64 | 65 | 使用字符串String 66 | 67 | 4. 社交网络 68 | 69 | 使用集合Set 70 | 71 | 5. 消息队列 72 | 73 | 一般不用。有专门的消息队列例如RocketMQ等,它们具有更好的封装性,功能更强大。 74 | 75 |
76 | 77 |
78 | 79 | ## Redis是否支持事务 80 | 81 | Redis 通过 MULTI(进入事务)、EXEC(执行)、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。 82 | 83 | 但Redis不支持回滚。Redis命令在事务中可能会执行失败,但是Redis事务不会回滚,而是继续会执行余下的命令。设计为“不支持回滚”是因为: 84 | 85 | - 只有当发生语法错误了(这个问题在命令队列时无法检测到),Redis命令才会执行失败。这意味着这些都是程序性错误,这类错误在开发的过程中就能够发现并解决掉,几乎不会出现在生产环境。 86 | - 由于不需要回滚,这使得Redis内部更加简单,而且运行速度更快。 87 | 88 | 可以参考:[Redis事务详解](https://baijiahao.baidu.com/s?id=1613631210471699441&wfr=spider&for=pc) 89 | 90 |
91 | 92 |
93 | 94 | ## 如何启动Redis 95 | 96 | 最好是通过配置文件.conf启动,具有良好的灵活性。 97 | 98 | 另外还有两种方式可以启动Redis,但是都不如通过配置文件启动好,只要知道可以修改.conf配置文件然后让Redis按照我们想要的配置来启动即可。 99 | 100 |
101 | 102 |
103 | 104 | ## Redis的关闭过程 105 | 106 | 优雅的关闭方式:使用redis-cli shutdown: 107 | 108 | 1. 断开与客户端的连接 109 | 2. 持久化文件生成(默认是RDB) 110 | 111 | 如果直接kill -9强制杀死Redis服务,不但不会做持久化操作,还会造成缓冲区等资源不能被优雅关闭,极端情况会造成AOF和复制数据丢失的情况。这和Redis宕机是一样的。 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/redis/数据结构.md: -------------------------------------------------------------------------------- 1 | Redis有五大数据结构。 2 | 3 | **目录** 4 | 5 | - [字符串String](#字符串String) 6 | - [哈希Hash](#哈希Hash) 7 | - [列表List](#列表List) 8 | - [集合Set](#集合Set) 9 | - [有序集合Zset](#有序集合Zset) 10 | 11 |
12 | 13 |
14 | 15 | ## 字符串String 16 | 17 | #### 内部编码 18 | 19 | 字符串类型的内部编码有三种: 20 | 21 | - int:8个字节的长整型 22 | - embstr:小于等于39个字节的字符串 23 | - raw:大于39个字节的字符串 24 | 25 | Redis根据当前值的类型和长度决定使用哪种编码实现。 26 | 27 |
28 | 29 | #### 使用场景 30 | 31 | 1. 缓存 32 | 2. 计数 33 | 3. 多应用服务的Session共享 34 | 35 |
36 | 37 |
38 | 39 | ## 哈希Hash 40 | 41 | #### 内部编码 42 | 43 | 哈希类型的内部编码有两种: 44 | 45 | - ziplist压缩列表 46 | 47 | - - 哈希类型元素个数小于512个,同时所有值都小于64字节时使用ziplist 48 | - ziplist使用紧凑结构实现多个元素的连续存储,在节省内存方面优秀,但操作时间长 49 | 50 | - hashtable哈希表 51 | 52 | - - 不满足ziplist条件时使用hashtable 53 | 54 |
55 | 56 | #### 使用场景 57 | 58 | 和字符串String基本相似。 59 | 60 |
61 | 62 |
63 | 64 | ## 列表List 65 | 66 | #### 内部编码 67 | 68 | 列表类型的内部编码有两种(3.2版本之前): 69 | 70 | - ziplist压缩列表 71 | 72 | - - 哈希类型元素个数小于512个,同时所有值都小于64字节时使用ziplist 73 | - ziplist使用紧凑结构实现多个元素的连续存储,在节省内存方面优秀,但操作时间长 74 | 75 | - linkedlist链表 76 | 77 | - - 不满足ziplist条件时使用linkedlist 78 | 79 | 列表类型的内部编码有两种(3.2版本之后): 80 | 81 | - 元素较少较小时使用ziplist 82 | - 多则使用quicklist 83 | 84 | **Quicklist**: 85 | 86 | 纯粹的使用 Linkedlist, 也就是普通链表来存储数据有两个弊端: 87 | 88 | 1. 每个节点都有自己的前后指针,指针所占用的内存有点多,太浪费了。 89 | 2. 每个节点单独的进行内存分配,当节点过多,造成的内存碎片太多了。影响内存管理的效率。 90 | 91 | 因此,定义了 quicklist, 将 linkedlist 和 ziplist 结合起来,形成一个 将多个 ziplist 通过前后指针互相连接起来的结构,可以在一定程度上缓解上面说到的两个问题。 92 | 93 |
94 | 95 | #### 使用场景 96 | 97 | 1. 消息队列 98 | 2. 文章列表 99 | 100 |
101 | 102 |
103 | 104 | ## 集合Set 105 | 106 | #### 内部编码 107 | 108 | 集合类型的内部编码有两种: 109 | 110 | - intset整数集合 111 | 112 | - - 集合中元素都是整数,且个数小于512时使用intset 113 | 114 | - hashtable哈希表 115 | 116 | - - 不满足intset条件时使用hashtable 117 | 118 |
119 | 120 | #### 使用场景 121 | 122 | 1. 标签(用户兴趣标签等) 123 | 124 |
125 | 126 |
127 | 128 | ## 有序集合Zset 129 | 130 | #### 内部编码 131 | 132 | 有序集合类型的内部编码有两种: 133 | 134 | - ziplist压缩列表 135 | 136 | - - 元素个数小于128个,同时所有值都小于64字节时使用ziplist 137 | 138 | - skiplist跳跃表 139 | 140 | - - 不满足ziplist条件时使用skiplist 141 | 142 |
143 | 144 | #### 使用场景 145 | 146 | 1. 排行榜(用户赞数等) -------------------------------------------------------------------------------- /docs/redis/持久化.md: -------------------------------------------------------------------------------- 1 | Redis支持两种持久化机制:RDB和AOF。 2 | 3 | **目录** 4 | 5 | - [RDB](#RDB) 6 | - [工作流程](#工作流程) 7 | - [优缺点](#优缺点) 8 | - [AOF](#AOF) 9 | - [工作流程](#工作流程) 10 | - [文件格式](#文件格式) 11 | - [追加阻塞](#追加阻塞) 12 | - [重启加载](#重启加载) 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | ## RDB 21 | 22 | RDB持久化是把当前进程数据生成快照保存到硬盘的过程。 23 | 24 | #### 工作流程 25 | 26 | ![RDB工作流程](https://gitee.com/jyannis/JavaLearning/raw/master/docs/redis/docs/RDB工作流程.png) 27 | 28 | Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。 29 | 30 |
31 | 32 | #### 优缺点 33 | 34 | **优点** 35 | 36 | - RDB是一个紧凑压缩的二进制文件,代表某个时间点上的数据快照,非常适合备份、全量复制等场景; 37 | - 恢复速度快,远快于AOF。 38 | 39 | **缺点** 40 | 41 | - 由于RDB是生成当时的快照,不能每时每刻都生成,没办法做到实时持久化。 42 | 43 |
44 | 45 |
46 | 47 | ## AOF 48 | 49 | AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。 50 | 51 | #### 工作流程 52 | 53 | ![AOF工作流程](https://gitee.com/jyannis/JavaLearning/raw/master/docs/redis/docs/AOF工作流程.png) 54 | 55 | 1. 所有写入命令追加到aof_buf(AOF缓冲)中; 56 | 2. AOF缓冲区根据对应的策略向硬盘做同步操作; 57 | 3. 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的; 58 | 4. 当Redis服务器重启时,可以加载AOF文件进行数据恢复。 59 | 60 | tips:**为什么需要aof_buf,而不是直接写磁盘?** 61 | 62 | 直接写磁盘导致性能完全取决于磁盘负载。先写入缓冲区的话,我们可以根据实际需求**调整缓冲区同步硬盘的策略**,在性能和安全方面做出平衡。 63 | 64 |
65 | 66 | #### 文件格式 67 | 68 | AOF命令写入的内容直接是文本协议格式。例如set hello world这条命令,在AOF缓冲区会追加如下文本: 69 | 70 | `\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n` 71 | 72 | 直接采取文本协议格式的优势如下: 73 | 74 | - 更好的兼容性 75 | - 更好的可读性 76 | - 避免二次处理开销(在存储格式和可处理格式之间转换) 77 | 78 |
79 | 80 | #### 同步策略 81 | 82 | Redis支持三种缓冲区同步文件策略(也就是上文说的**缓冲区同步硬盘的策略**), 83 | 84 | 由参数appendfsync控制,不同值的含义如下: 85 | 86 | | **可配置值** | **说明** | 87 | | ------------ | ------------------------------------------------------------ | 88 | | always | 每次命令写入aof_buf都会调用系统fsync同步 | 89 | | **everysec** | **写入aof_buf后调用系统write。由专门线程负责每秒调用fsync** | 90 | | no | 写入aof_buf后调用系统write,不进行fysnc。由操作系统自行控制fsync | 91 | 92 | 默认everysec,它也是一般推荐的配置。其他两个不太实用。 93 | 94 | tips: 95 | 96 | - 系统**write**操作 97 | 98 | write操作在写入系统缓冲区后直接返回,同步硬盘操作依赖于操作系统调度机制。 99 | 100 | - 系统**fsync**操作 101 | 102 | fsync操作针对单个文件操作(例如AOF文件)做强制硬盘同步。fsync将阻塞直到写入硬盘完成后返回,确保数据持久化。 103 | 104 |
105 | 106 | #### 追加阻塞 107 | 108 | ![AOF追加阻塞](https://gitee.com/jyannis/JavaLearning/raw/master/docs/redis/docs/AOF阻塞流程.png) 109 | 110 | 1. 主线程负责写入AOF缓冲区 111 | 112 | 2. AOF线程负责每秒执行一次fsync,并记录最近一次同步时间 113 | 114 | 3. 主线程负责对比上次AOF同步时间: 115 | 116 | 4. 1. 如果距上次同步成功时间在2秒内,主线程直接返回; 117 | 2. 如果距上次同步成功时间超过2秒,主线程将阻塞,直到同步完成。 118 | 119 | tips:也就是说,在everysec配置下,系统发生宕机时,最多可能丢失2秒数据,而不是1秒。 120 | 121 |
122 | 123 |
124 | 125 | ## 重启加载 126 | 127 | AOF和RDB都可以用于服务器重启时的数据恢复。流程如下: 128 | 129 | ![重启加载](https://gitee.com/jyannis/JavaLearning/raw/master/docs/redis/docs/重启加载.png) -------------------------------------------------------------------------------- /docs/Spring/Spring AOP.md: -------------------------------------------------------------------------------- 1 | 框架是JAVA面试中比较重要的一个部分,而Spring是面试最爱问的框架之一。Spring中最重要的就是IOC和AOP这两大概念,本篇中对于AOP相关知识及面试题作一个总结。 2 | 3 |
4 | 5 | **目录** 6 | 7 | - [基本概念](#基本概念) 8 | - [性质](#性质) 9 | - [目的](#目的) 10 | - [原理](#原理) 11 | - [AspectJ静态代理](#AspectJ静态代理) 12 | - [SpringAOP动态代理](#SpringAOP动态代理) 13 | - [AOP关键词](#AOP关键词) 14 | - [动态代理的两种实现](#动态代理的两种实现) 15 | - [JDK动态代理](#JDK动态代理]) 16 | - [CGLIB动态代理](#CGLIB动态代理) 17 | - [其他一些核心问题](#其他一些核心问题) 18 | 19 |
20 | 21 |
22 | 23 | ## AOP基本概念 24 | 25 | AOP:Aspect Oriented Programming 也即**面向切面编程** 26 | 27 | 下面分三个方面来陈述: 28 | 29 | - 性质:AOP本质上是一个什么东西。 30 | - 目的:AOP之所以存在,是为了解决什么问题? 31 | - 原理:AOP背后是如何实现的? 32 | 33 |
34 | 35 |
36 | 37 | ### 性质 38 | 39 | AOP是一种**设计思想**。 40 | 41 | 就像OOP是面向对象编程一样,AOP则是OOP的一种延伸,是针对切面进行编程。 42 | 43 | 那么什么叫**切面**呢?可以认为是一个可重用的模块,或是一系列公共行为和逻辑。 44 | 45 | 如果具体举例子来说,比如权限认证(用户在进入任何功能性接口之前都应该先完成鉴权)、日志打印(许多接口出现问题的时候都应该有相应的异常日志)等等。 46 | 47 |
48 | 49 |
50 | 51 | ### 目的 52 | 53 | 借助AOP,我们希望: 54 | 55 | - 通过尽可能地复用代码,以降低编码复杂度 56 | - 通过把切面提取出来,以降低模块间的耦合度 57 | - 联系上面这两条,同时也就提升了系统的可维护性 58 | 59 |
60 | 61 |
62 | 63 | ### 原理 64 | 65 | AOP实现的关键在于**代理模式**,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。 66 | 67 | #### AspectJ静态代理 68 | 69 | AspectJ会在编译阶段生成AOP代理类,因此也称为编译时增强。 70 | 71 | 它会在编译阶段将Aspect(切面)织入到Java字节码中,运行的时候利用到的就是增强之后的AOP对象。 72 | 73 | #### SpringAOP动态代理 74 | 75 | Spring AOP不会像AspectJ那样在编译时增强,而是每次在运行时于内存中临时为方法生成一个AOP对象。 76 | 77 | 这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。 78 | 79 | 如果理解了动态代理,那么对Spring AOP动态代理也不会难以理解。希望对动态代理不是很熟悉的读者可以先自行查阅相关资料学习。 80 | 81 |
82 | 83 |
84 | 85 | ## AOP关键词 86 | 87 | 在Java AOP的世界里,有一些关键词是一定要明白的。 88 | 89 |
90 | 91 | **Aspect(切面)** 92 | 93 | Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。 94 | 95 | **Joint point(连接点)** 96 | 97 | 表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。 98 | 99 | **Pointcut(切点)** 100 | 101 | 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。 102 | 103 | **Advice(增强)** 104 | 105 | Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 106 | 107 | **Advice执行顺序(很重要!!)** 108 | 109 | 环绕(@Around)开始阶段 → @Before → 执行切点方法 → 环绕结束阶段 → @After → @AfterReturning 110 | 111 | **Target(目标对象)** 112 | 113 | 织入 Advice 的目标对象。 114 | 115 | **Weaving(织入)** 116 | 117 | 将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。 118 | 119 |
120 | 121 |
122 | 123 | ## 动态代理的两种实现 124 | 125 | 在上文的阅读中,我们知道Spring AOP是基于动态代理实现的。 126 | 127 | 它背后其实有两种实现方式:JDK动态代理和CGLIB动态代理。这也是非常重要的知识点,在下文中详细说明。 128 | 129 |
130 | 131 | ### JDK动态代理 132 | 133 | 利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类, 134 | 135 | 在调用具体方法前调用InvokeHandler来处理。 136 | 137 | https://blog.csdn.net/yhl_jxy/article/details/80586785 138 | 139 |
140 | 141 | ### CGLIB动态代理 142 | 143 | 利用ASM开源包,修改代理对象类的class文件的字节码,生成子类,再加载class文件。 144 | 145 | https://blog.csdn.net/yhl_jxy/article/details/80633194 146 | 147 |
148 | 149 |
150 | 151 | ### 其他一些核心问题 152 | 153 | #### 何时使用JDK还是CGLIB? 154 | 155 | - 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。 156 | 157 | - 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。 158 | 159 | - 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。 160 | 161 |
162 | 163 | #### JDK动态代理和CGLIB字节码生成的区别? 164 | 165 | - JDK动态代理只能对实现了接口的类生成代理,而不能针对类。 166 | 167 | - CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法, 168 | 169 | ​ 并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final, 170 | 171 | ​ 对于final类或方法,是无法继承的。private也是一样。 172 | 173 |
174 | 175 | #### CGlib比JDK快? 176 | 177 | - 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。 178 | 179 | - 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。 180 | 181 |
182 | 183 | #### Spring如何选择用JDK还是CGLIB? 184 | 185 | - 当Bean实现接口时,Spring就会用JDK的动态代理。 186 | 187 | - 当Bean没有实现接口时,Spring使用CGlib实现。 188 | 189 | - 可以强制使用CGlib(在spring配置中加入)。 -------------------------------------------------------------------------------- /docs/IO/一文读懂BIO、NIO、AIO.md: -------------------------------------------------------------------------------- 1 | 在上篇[IO(上)——基本概念](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483976&idx=1&sn=f6206aebd57292948b82cd38c8ac4071&chksm=9ffc602ea88be938b7f53337c48a1bab8b04242777f2867f443c604cc8278f592ec7b1f8df1d&token=2111105882&lang=zh_CN#rd)中,我们初步认识了同步异步、阻塞非阻塞的含义,并了解了与之对应的IO模型。 2 | 3 | 本篇文章中我们将这些基本概念具体化,探讨JAVA实现层面的IO。 4 | 5 | 在JAVA层面,我们需要熟悉的IO有三种:BIO,NIO,AIO。 6 | 7 | 本篇中,我们主要从“是什么”、“有什么用”、“怎么实现的”三个角度切入分析。 8 | 9 | 是什么——也即“基本概念”。 10 | 11 | 有什么用——也即“适用场景”。 12 | 13 | 有什么用——也即“基本原理”。 14 | 15 |
16 | 17 |
18 | 19 | ## BIO(Blocking I/O) 20 | 21 | ### 基本概念 22 | 23 | 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。它是JDK4之前的唯一选择。 24 | 25 |
26 | 27 | ### 使用场景 28 | 29 | 在连接数比较少的情况下,它还是很有用的,因为编码起来很方便,思路很简单清晰。 30 | 31 | 但是对于十万级百万级连接会有些吃力。 32 | 33 |
34 | 35 | ### 基本原理 36 | 37 | 采用 **BIO 通信模型** 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。 38 | 39 | 我们通过自旋的方式监听请求,一旦接收到一个连接请求,就会建立通信套接字(Socket)进行读写。 40 | 41 | 此时不能再接收其他连接请求,只能等待同当前连接下的操作执行完成。 42 | 43 | 不过可以通过多线程来支持多个客户端的连接,如下图所示。 44 | 45 | ![BIO基本原理](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/BIO%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86.png) 46 | 47 | 如果要让 **BIO 通信模型** 能够同时处理多个请求,就必须使用多线程,也就是在接收到连接请求后为每个请求创建一个新的线程进行处理。 48 | 49 | 处理完成之后,通过输出流返回应答给客户端,线程销毁。 50 | 51 | 考虑到如果某个连接还没有释放,但又不做任何事情的话就会造成不必要的线程开销,因而可以通过 **线程池机制** 改善。传送门:[线程池知识总结](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483763&idx=1&sn=73501f177d4882a23401e614b7a06b5d&chksm=9ffc6315a88bea030635a6fc138fe4d41b10a2801c04f7eefdbe642b0317d5a9de98e627edef&scene=21#wechat_redirect) 52 | 53 |
54 | 55 |
56 | 57 | ## NIO(NEW I/O) 58 | 59 | ### 基本概念 60 | 61 | JDK4开始支持。同步非阻塞I/O,表面上总体和BIO类似,但使用了多路复用技术,一个线程可以监听多个IO操作请求。 62 | 63 | 64 | 65 | 以下是一些分析性文本: 66 | 67 | NIO是同步的多路复用IO。在上篇[IO(上)——基本概念](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483976&idx=1&sn=f6206aebd57292948b82cd38c8ac4071&chksm=9ffc602ea88be938b7f53337c48a1bab8b04242777f2867f443c604cc8278f592ec7b1f8df1d&token=2111105882&lang=zh_CN#rd)中,我们提到网上经常称NIO为Non-Blocking,也就是非阻塞IO,但是同步和非阻塞是无法同时满足的。那这到底是为什么呢? 68 | 69 | 这是因为NIO的非阻塞,指的是对于一个IO请求来看,是非阻塞的。NIO采取多路复用,一个线程可能会负责多个IO请求,在一个IO请求处理完后,线程并不会阻塞在哪里,而是可能去处理其他的IO请求。所以称之为非阻塞。 70 | 71 | > tip:如果不了解IO多路复用,可能对上面这段话无法理解,请先继续看下去。 72 | 73 |
74 | 75 | ### 使用场景 76 | 77 | NIO适用于IO连接请求数目多但连接时间普遍较短的业务场景。 78 | 79 |
80 | 81 | ### 基本原理 82 | 83 | #### IO多路复用(multiplex) 84 | 85 | 我们先用通俗的话来讲一下多路复用是什么,这里借鉴一下知乎上不错的一篇回答(我有稍加修改): 86 | 87 | > 假设你是一个老师,让30个学生解答一道题目,然后检查学生做的是否正确,你有下面几个选择: 88 | > 89 | > \1. 第一种选择:**按顺序逐个检查**,先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡主,全班都会被耽误。 90 | > 91 | > 这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。(诶个进行BIO) 92 | > 93 | > \2. 第二种选择:你**创建30个分身**,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。(用三十个线程同时去做BIO) 94 | > 95 | > \3. 第三种选择,你**站在讲台上等,谁解答完谁举手**。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A…… 96 | > 97 | > 这种就是IO多路复用。 98 | > 99 | > 100 | > 101 | > 作者:柴小喵 102 | > 链接:https://www.zhihu.com/question/28594409/answer/52835876 103 | 104 |
105 | 106 | #### NIO核心组件 107 | 108 | NIO核心组件包括:Channel通道、Buffer缓冲、Selector选择器。利用这三个核心组件,构成新IO的强大优势。 109 | 110 | - Channel 111 | - Buffer 112 | - Selector 113 | 114 |
115 | 116 | ##### 通道(Channel) 117 | 118 | NIO 通过Channel(通道) 进行读写。 119 | 120 | 就好像流操作都要基于Stream一样,NIO中的所有I/O操作都基于Channel对象。 121 | 122 | 一个Channel代表和某一实体(文件、网络套接字Socket等)的连接。 123 | 124 | 通过Channel,可以读取数据到Buffer中,也可以把Buffer中的数据写入通道。 125 | 126 | | **区别** | **Stream** | **Channel** | 127 | | ------------------ | -------------- | ------------------------------------------------ | 128 | | 支持异步 | 不支持 | 支持 | 129 | | 是否可双向传输数据 | 不能,只能单向 | 可以,既可以从通道读取数据,也可以向通道写入数据 | 130 | | 是否结合Buffer使用 | 不 | 必须结合Buffer使用 | 131 | | 性能 | 较低 | 较高 | 132 | 133 |
134 | 135 | ##### 缓冲(Buffer) 136 | 137 | NIO中所使用的缓冲区是封装过的Buffer类,而不是简单的byte数组等等。 138 | 139 | 借助Buffer提供的API,我们可以灵活地操作数据。 140 | 141 | **Buffer本质上就是一块内存区**,可以用来写入数据,并在稍后读取出来。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中,可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。 142 | 143 | 最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区。每一种Java基本类型(除了Boolean)都对应有一种缓冲区。 144 | 145 |
146 | 147 | ##### 选择器(Selector) 148 | 149 | NIO的IO多路复用是利用选择器实现的,它是Java NIO核心组件之一。 150 | 151 | Selector用于采集各个Channel的状态(或者说事件)。 152 | 153 | 我们将多个Channel注册到Selector中,由一个线程进行监听,当事件触发时再进行处理,以实现IO多路复用。 154 | 155 | 这些事件包含: 156 | 157 | - Accept:有可接受的连接 158 | - Connect:连接成功 159 | - Read:有数据可读 160 | - Write:可以写入数据 161 | 162 | ![Selector](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/Selector.png) 163 | 164 |
165 | 166 |
167 | 168 | ## AIO(Asynchronous I/O) 169 | 170 | ### 基本概念 171 | 172 | JDK7开始支持。异步I/O,基于事件和回调机制实现。 173 | 174 |
175 | 176 | ### 使用场景 177 | 178 | AIO适用于IO连接请求数目多且连接时间往往较长的业务场景。 179 | 180 |
181 | 182 | ### 基本原理 183 | 184 | JAVA AIO框架在windows下使用windows IOCP技术,在Linux下使用epoll多路复用IO技术模拟异步IO。 185 | 186 | 在JAVA AIO框架中,由于应用程序不是轮询方式,而是事件触发和回调的方式,所以不再需要选择器(Selector)了,改由通道(Channel)直接到操作系统注册监听。 187 | 188 | JAVA NIO和JAVA AIO框架,除了因为操作系统的实现不一样而去掉了Selector外,其他的重要概念都是存在的,例如上文中提到的Channel的概念,还有基于Buffer缓冲处理。 -------------------------------------------------------------------------------- /docs/IO/操作系统层面的IO.md: -------------------------------------------------------------------------------- 1 | 在上篇[IO(中)——深入理解JAVA层面的IO](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483980&idx=1&sn=1f7ae34cf2dc93b919b68dc5c90be443&chksm=9ffc602aa88be93c21fd2c298950e3c080d6a5384af906fd2f654209e61e9cee0a6a7667fce2&token=1958345249&lang=zh_CN#rd)中,我们认识了JAVA对于IO的封装:BIO、NIO、AIO。 2 | 3 | 而我们也知道,Java中提供的IO有关的API,在进行处理的时候,其实是依赖操作系统层面的IO操作实现的。 4 | 5 | 本篇文章中我们就继续探索下去,了解一下在Linux下的五种IO模型: 6 | 7 | - 阻塞IO模型 8 | - 非阻塞IO模型 9 | - 多路复用IO模型 10 | - 信号驱动IO模型 11 | - 异步IO模型 12 | 13 | > tips: 14 | > 15 | > 在下文中提到的“应用程序”、“用户进程”、“进程”普遍都指同一个概念。 16 | 17 |
18 | 19 |
20 | 21 | ## 阻塞IO模型 22 | 23 | ### 基本含义 24 | 25 | 和[IO(中)——深入理解JAVA层面的IO](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483980&idx=1&sn=1f7ae34cf2dc93b919b68dc5c90be443&chksm=9ffc602aa88be93c21fd2c298950e3c080d6a5384af906fd2f654209e61e9cee0a6a7667fce2&token=1958345249&lang=zh_CN#rd)中提到的BIO含义相似。 26 | 27 | 进程等待某个条件,如果条件不满足,则一直等下去。条件满足,则进行下一步操作。 28 | 29 | 30 | 31 | ### 操作流程 32 | 33 | ![阻塞IO模型](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/阻塞IO模型.png) 34 | 35 | 1. 应用程序通过系统调用`recv`或`recvfrom`尝试接收数据 36 | 2. 内核开始准备数据,其间应用程序**阻塞等待** 37 | 3. 内核准备好数据,并将数据从**内核空间**复制到**用户空间** 38 | 4. 应用程序处理数据 39 | 40 |
41 | 42 | > 阻塞IO模型下,应用程序可能耗费在等待上的时间过长,浪费了许多性能。 43 | > 44 | > 那么如何减少等待时间呢?就引入了非阻塞IO模型。 45 | 46 |
47 | 48 |
49 | 50 | ## 非阻塞IO模型 51 | 52 | ### 基本含义 53 | 54 | 与**阻塞IO模型**的不同之处在于,在非阻塞IO模型下,应用程序在等待数据时是采取**轮询**的方式,而不是**阻塞**的方式。 55 | 56 | 这就可以类比到JAVA里Synchronized悲观锁和CAS乐观锁的区别。 57 | 58 |
59 | 60 | ### 操作流程 61 | 62 | ![非阻塞IO模型](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/非阻塞IO模型.png) 63 | 64 | 如果理解了前面的阻塞IO模型,对于现在这个非阻塞IO模型应该也很好理解。 65 | 66 | 应用程序轮询内核是否已准备好数据,直到准备好后对数据进行处理。 67 | 68 | 非阻塞的优势就在于,在连续两次轮询之间,应用程序可以去做一些别的事情。 69 | 70 |
71 | 72 | > 非阻塞IO模型下,应用程序耗费在等待上的时间少了一些。 73 | > 74 | > 但高频度的轮询,依然可能导致性能有比较大的损耗。 75 | > 76 | > 那能不能不由应用程序反复去问内核,而是由内核主动通知应用程序呢? 77 | > 78 | > 这就引入了信号驱动IO模型。 79 | 80 |
81 | 82 |
83 | 84 | ## 信号驱动IO模型 85 | 86 | ### 基本含义 87 | 88 | 信号驱动IO模型下,以通知而非轮询的方式完成数据的等待,然后再由应用程序完成对数据的处理。 89 | 90 |
91 | 92 | ### 操作流程 93 | 94 | ![信号驱动IO模型](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/信号驱动IO模型.png) 95 | 96 | 1. 应用程序向内核注册一个信号处理程序 97 | 98 | 注册是一个及时返回的操作,不会产生阻塞。 99 | 100 | 2. 内核准备好数据,对应用程序递交信号 101 | 3. 应用程序通过系统调用`recv`或`recvfrom`尝试接收数据 102 | 4. 内核将数据从内核空间复制到用户空间(期间应用程序阻塞) 103 | 5. 数据复制完成,应用程序开始处理数据 104 | 105 |
106 | 107 | > 信号驱动IO模型下,已经最大化节省了等待的时间。 108 | > 109 | > 但是由于需要引入信号,在实现复杂性上就会稍高一些。 110 | > 111 | > 事实上我们发现,还有更好的方式来提升性能,这就引入了下面的多路复用IO模型。 112 | 113 |
114 | 115 |
116 | 117 | ## 多路复用IO模型 118 | 119 | ### 基本含义 120 | 121 | 和[IO(中)——深入理解JAVA层面的IO](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483980&idx=1&sn=1f7ae34cf2dc93b919b68dc5c90be443&chksm=9ffc602aa88be93c21fd2c298950e3c080d6a5384af906fd2f654209e61e9cee0a6a7667fce2&token=1958345249&lang=zh_CN#rd)中提到的NIO含义相似。 122 | 123 | 多路复用IO模型下,多个应用程序(即多个进程)的IO注册到同一个管道里,由管道代替它们与内核交互。当内核准备好了任何一个应用程序的数据,就由管道通知应用程序去接收。 124 | 125 |
126 | 127 | ### 操作流程 128 | 129 | ![多路复用IO模型](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/多路复用IO模型.png) 130 | 131 | 1. 应用程序注册到select上 132 | 133 | 多个应用程序可以注册到同一个select 134 | 135 | 2. select阻塞,等待数据 136 | 3. 内核准备好了某个应用程序A的数据,select返回,通知应用程序 137 | 4. 应用程序通过系统调用`recv`或`recvfrom`尝试接收数据 138 | 5. 内核将数据从内核空间复制到用户空间(期间应用程序阻塞) 139 | 6. 数据复制完成,应用程序开始处理数据 140 | 141 |
142 | 143 | > 多路复用IO模型首先利用到了信号驱动的优势(select需要通知应用程序数据准备好了,所以也算是一种信号驱动) 144 | > 145 | > 另外,多路复用让一个select服务多个应用程序,采用一对多的方式,节省了一定的资源成本。 146 | > 147 | > 但是在数据复制(内核空间 → 用户空间)阶段,依然无法避免应用程序的阻塞。 148 | > 149 | > 为了优化数据复制时阻塞的问题,引入了下面的异步IO模型。 150 | 151 |
152 | 153 |
154 | 155 | ## 异步IO模型 156 | 157 | ### 基本含义 158 | 159 | 和[IO(中)——深入理解JAVA层面的IO](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483980&idx=1&sn=1f7ae34cf2dc93b919b68dc5c90be443&chksm=9ffc602aa88be93c21fd2c298950e3c080d6a5384af906fd2f654209e61e9cee0a6a7667fce2&token=1958345249&lang=zh_CN#rd)中提到的AIO含义相似。 160 | 161 | 在异步IO模型下,应用程序永远不会阻塞,发起IO请求之后就可以做自己的事情,直到数据准备且复制完成,再去处理数据。 162 | 163 |
164 | 165 | ### 操作流程 166 | 167 | ![异步IO模型](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/异步IO模型.png) 168 | 169 | 1. 用户进程发起aio_read,开始做其他事情 170 | 171 | 2. 内核准备数据 172 | 3. 内核把数据从内核空间复制到用户空间 173 | 4. 内核通知用户进程数据已准备完毕 174 | 5. 用户进程开始处理数据 175 | 176 |
177 | 178 | > 多路复用IO模型首先利用到了信号驱动的优势(select需要通知应用程序数据准备好了,所以也算是一种信号驱动) 179 | > 180 | > 异步IO现在使用已愈趋广泛,但由于一些底层技术及框架的局限性,在很多应用领域还未能彻底替代多路复用IO。 181 | 182 |
183 | 184 |
185 | 186 | ## 五种IO模型的比较 187 | 188 | 最后我们来用一张图和一个表格来比较一下五种IO模型。 189 | 190 | 图源于网络,表格由我自行整理。 191 | 192 | ![五种IO模型](https://gitee.com/jyannis/JavaLearning/raw/master/docs/IO/docs/五种IO模型.jpg) 193 | 194 | | IO模型 | 数据准备阶段 | 数据复制阶段 | 使用场景 | 195 | | ---------- | ------------ | ------------ | ------------------------------------------------------------ | 196 | | 阻塞IO | 阻塞 | 阻塞 | 连接少,IO任务简单的场景 | 197 | | 非阻塞IO | 非阻塞 | 阻塞 | 和阻塞IO差不多,比阻塞IO的能力强一些 | 198 | | 信号驱动IO | 非阻塞 | 阻塞 | 它性能没有复用IO和异步IO强,信号使用起来又复杂,一般不太使用 | 199 | | 多路复用IO | 非阻塞 | 阻塞 | 连接多,IO任务简单的场景 | 200 | | 异步IO | 非阻塞 | 非阻塞 | 连接多,IO任务复杂的场景 | 201 | 202 | -------------------------------------------------------------------------------- /docs/JVM/JMM Java内存模型.md: -------------------------------------------------------------------------------- 1 | JMM(JAVA内存模型)是JVM基础知识中最容易被忽略的部分,但它对于我们加深对JVM总体的理解起到举足轻重的作用,同时也是大厂面试的一个重要考点。 2 | 3 |
4 | 5 | **目录** 6 | 7 | - [基本概念](#基本概念) 8 | - [性质](#性质) 9 | - [目的](#目的) 10 | - [内容](#内容) 11 | - [规范变量访问](#规范变量访问) 12 | - [规范原子性](#规范原子性) 13 | - [规范可见性](#规范可见性) 14 | - [规范有序性](#规范有序性) 15 | - [注意事项](#注意事项) 16 | - [面试中如何回答](#面试中如何回答) 17 | 18 |
19 | 20 |
21 | 22 | ## 基本概念 23 | 24 | JMM:JAVA Memory Model 也即**JAVA内存模型** 25 | 26 | 下面分四个方面来陈述: 27 | 28 | - 性质:JMM本质上是一个什么东西。 29 | - 目的:JMM之所以存在,是为了解决什么问题? 30 | - 内容:JMM为了实现它的目的,要做些什么? 31 | - 实现:利用什么技术真正实现它的目的? 32 | 33 |
34 | 35 |
36 | 37 | ### 性质 38 | 39 | JMM是一种**规范**。 40 | 41 | 就好像HTTP是一种**交互协议**,[JAVA运行时数据区](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483776&idx=1&sn=25fbf98bb6dcda4e924675dc1e64997e&chksm=9ffc63e6a88beaf053d6036e5c4fff87af41df7d5a6e35329e115f9123b9b2d5c4363068f4c2&token=1776113537&lang=zh_CN#rd)是一种**内存结构**一样,JMM是对JAVA虚拟机作出**规范**,使它必须符合某些要求以满足某些目的。 42 | 43 |
44 | 45 |
46 | 47 | ### 目的 48 | 49 | 借助JMM规范,我们希望能够做到: 50 | 51 | - 屏蔽硬件和操作系统的访问差异,让Java程序在各种平台下都能达到一致的内存访问效果; 52 | - 解决多线程通过共享内存进行通信时,存在的原子性、可见性、有序性问题。 53 | 54 |
55 | 56 |
57 | 58 | ### 内容 59 | 60 | #### 规范变量访问 61 | 62 | JMM的首要目标是定义程序中各个变量的访问规则,即在虚拟机中**将变量存储到内存**和**从内存中取出变量**这样的底层细节。 63 | 64 |
65 | 68 |
69 |
线程、主内存、工作内存三者的交互关系
73 |
74 | 75 | - JMM规定了所有的变量都存储在主内存(Main Memory)中 76 | - 每个线程还有自己的工作内存(Work Memory),其中保存被该线程使用到的变量的主内存副本拷贝 77 | - 线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,不能直接读写主内存 78 | - 线程间变量值的传递需要通过主内存,不能访问其他线程的工作内存 79 | 80 |
81 | 82 | #### 规范原子性 83 | 84 | JMM要求: 85 | 86 | 除了64位的数据类型(long和double),其他基本数据类型的访问读写都是必须具备原子性的。 87 | 88 | 不过现在的商用虚拟机都会把long和double数据类型也实现为原子操作。 89 | 90 | 另外,为了能够实现更大范围的原子性保证,JMM还提供了lock和unlock两个原子操作来满足这种需求,与它们相对应的更高层次的字节码也就是`monitorenter`和`moniterexit`,也就是synchronized的底层实现方式。还不是很了解synchronized?传送门:[Synchronizd关键字](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483769&idx=1&sn=bdb296488a0b2b30ae98287ecd7151cf&chksm=9ffc631fa88bea09aed92f62d1c2a8b6d469ec918586fa4e011b72c304bcb8ae5541465de9d3&token=1776113537&lang=zh_CN#rd) 91 | 92 |
93 | 94 | #### 规范可见性 95 | 96 | 可见性是指**当一个线程修改了共享变量的值,其他线程能够立即得知这个修改**。 97 | 98 | 为了规范可见性,JMM引入了volatile关键字。volatile保证了: 99 | 100 | 1. 被volatile修饰的变量在被修改后立即同步到主内存 101 | 2. 被volatile修饰的变量在每次被使用之前都从主内存刷新 102 | 103 | 因此,可以使用volatile来保证多线程操作时变量的可见性。 104 | 105 | 而具体如何保证上面两条,**内存屏障**也起到至关重要的作用,篇幅原因这里就不详细展开了。如果想对volatile有更进一步了解,请移步[Volatile关键字](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483765&idx=1&sn=cf0c1b74985e2389719d6ae1f51f4258&chksm=9ffc6313a88bea05f7c77c45247a5a1ce251b77b13c71c0068a4b30cc2effee2e80e21e59988&token=1776113537&lang=zh_CN#rd) 106 | 107 | 108 | 109 | 除了volatile以外,还有两个关键字可以实现可见性: 110 | 111 | - synchronized 112 | 113 | 同步块的可见性是由“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)”这条规则获得的 114 | 115 | - final 116 | 117 | 被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把“this”的引用传递出去(**this引用逃逸**是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见final修饰的值。 118 | 119 |
120 | 121 | #### 规范有序性 122 | 123 | 规范程序的有序性,也即让程序指令能够按照想要的顺序来执行。 124 | 125 | 这里的程序指令并不是一行行代码,而是应该理解为一个个原子的操作指令,它是比一行行代码更细化的粒度。 126 | 127 | JMM中利用三点来规范有序性: 128 | 129 | 1. volatile关键字防止指令重排 130 | 131 | volatile是通过**内存屏障**来防止指令重排的,如果想对volatile有更进一步了解,请移步[Volatile关键字](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483765&idx=1&sn=cf0c1b74985e2389719d6ae1f51f4258&chksm=9ffc6313a88bea05f7c77c45247a5a1ce251b77b13c71c0068a4b30cc2effee2e80e21e59988&token=1776113537&lang=zh_CN#rd) 132 | 133 | 2. as-if-serial语义 134 | 135 | 含义是:“在单线程下,无论指令怎样排序,最终结果都不能被改变。” 136 | 137 | 有了as-if-serial语义,才使得synchronized也能够保证有序性。 138 | 139 | 3. happens-before"先行发生"原则 140 | 141 | “先行发生”原则定义两项操作之间的偏序关系。包括了“程序次序规则”、“线程启动规则”、“传递性”等等。具体如下(简单了解即可,无需记忆): 142 | 143 | > 程序次序规则:同一个线程内,按照代码出现的顺序,前面的代码先行于后面的代码,准确的说是控制流顺序,因为要考虑到分支和循环结构。 144 | > 145 | > 146 | > 147 | > 管程锁定规则:一个unlock操作先行发生于后面(时间上)对同一个锁的lock操作。 148 | > 149 | > 150 | > 151 | > volatile变量规则:对一个volatile变量的写操作先行发生于后面(时间上)对这个变量的读操作。 152 | > 153 | > 154 | > 155 | > 线程启动规则:Thread的start( )方法先行发生于这个线程的每一个操作。 156 | > 157 | > 158 | > 159 | > 线程终止规则:线程的所有操作都先行于此线程的终止检测。可以通过Thread.join( )方法结束、Thread.isAlive( )的返回值等手段检测线程的终止。 160 | > 161 | > 162 | > 163 | > 线程中断规则:对线程interrupt( )方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupt( )方法检测线程是否中断 164 | > 165 | > 166 | > 167 | > 对象终结规则:一个对象的初始化完成先行于发生它的finalize()方法的开始。 168 | > 169 | > 170 | > 171 | > 传递性:如果操作A先行于操作B,操作B先行于操作C,那么操作A先行于操作C。 172 | 173 |
174 | 175 |
176 | 177 | ## 注意事项 178 | 179 | 经常有人把JAVA内存模型和运行时数据区搞混,也就是下面两张图: 180 | 181 |
182 | 185 |
186 |
线程、主内存、工作内存三者的交互关系
190 |
191 | 192 |
193 | 194 |
195 | 198 |
199 |
Java运行时数据区
203 |
204 | 205 | 请千万不要再搞混了。 206 | 207 |
208 | 209 |
210 | 211 | ## 面试中如何回答 212 | 213 | 面试官:**JMM了解吗?简单讲讲?** 214 | 215 | 216 | 217 | 答: 218 | 219 | JMM,JAVA内存模型,是一种用来屏蔽各种硬件和操作系统的内存访问差异的规范,目的是解决多线程通过主内存通信时,存在的原子性、可见性、有序性问题。 220 | 221 | JMM首先规范了变量的访问。它要求所有变量都要存在主内存中,每个线程在自己工作内存中操作变量,然后同步回主内存; 222 | 223 | 其次JMM利用内存间原子性的交互操作规范了原子性,例如lock、unlock、read、store等等。通过这些操作,JMM保证了基本数据类型操作的原子性,并支持了synchronized; 224 | 225 | JMM还利用volatile实现了可见性,它要求被volatile修饰的变量在使用时与主存及时同步; 226 | 227 | 最后JMM还保证了有序性,这是由volatile关键字的内存屏障、as-if-serial语义,以及happens-before“先行发生”原则一同支持的。 228 | 229 | > 以上是概述性回答,然后根据面试官提的问题,可以再详细展开。例如:“面试官:‘volatile怎么保证可见性的?'”就可以再讲讲主内存与工作内存的及时性同步,还有内存屏障等等。 230 | 231 | -------------------------------------------------------------------------------- /docs/Spring/Spring IOC.md: -------------------------------------------------------------------------------- 1 | 框架是JAVA面试中比较重要的一个部分,而Spring是面试最爱问的框架之一。Spring中最重要的就是IOC和AOP这两大概念,本篇中对于IOC相关知识及面试题作一个总结。 2 | 3 |
4 | 5 | **目录** 6 | 7 | - [基本概念](#基本概念) 8 | - [性质](#性质) 9 | - [目的](#目的) 10 | - [内容](#内容) 11 | - [没有引入IOC的软件系统](#没有引入IOC的软件系统) 12 | - [引入IOC的软件系统](#引入IOC的软件系统) 13 | - [引入IOC的效果](#引入IOC的效果) 14 | - [常见面试题](#常见面试题) 15 | - [Bean作用域](#(1)Bean作用域) 16 | - [Bean生命周期](#(2)Bean生命周期) 17 | - [BeanFactory和ApplicationContext的区别](#(3)BeanFactory和ApplicationContext的区别) 18 | - [核心源码梳理](#核心源码梳理) 19 | - [Bean解析](#(1)Bean解析) 20 | - [Bean注册](#(2)Bean注册) 21 | - [Bean加载](#(3)Bean加载) 22 | 23 |
24 | 25 |
26 | 27 | ## IOC基本概念 28 | 29 | IOC:Inverse of Control 也即**控制反转** 30 | 31 | 下面分四个方面来陈述: 32 | 33 | - 性质:IOC本质上是一个什么东西。 34 | - 目的:IOC之所以存在,是为了解决什么问题? 35 | - 内容:IOC为了实现它的目的,要做些什么? 36 | 37 |
38 | 39 |
40 | 41 | ### 性质 42 | 43 | IOC是一种**设计思想**。 44 | 45 | 它不是一种具体的技术,虽然我们经常把IOC和Spring联系在一起,但其实它也完全可以体现在其他的框架和语言中。 46 | 47 |
48 | 49 |
50 | 51 | ### 目的 52 | 53 | 借助IOC设计思想,我们希望能够做到**互相依赖的对象之间的解耦**。 54 | 55 |
56 | 57 |
58 | 59 | ### 内容 60 | 61 | #### 没有引入IOC的软件系统 62 | 63 | 软件系统内部肯定有若干个对象存在,其中部分与部分对象之间存在着互相调用或依赖的关系,导致对象之间耦合度较高,如下图: 64 | 65 | ![耦合的对象](https://gitee.com/jyannis/doc/raw/master/JavaLearning/Spring/%E8%80%A6%E5%90%88%E7%9A%84%E5%AF%B9%E8%B1%A1.png) 66 | 67 | 我们可以把对象比喻成齿轮。如果几个耦合的对象中有一个对象不动了(无法正常工作),那么其他的对象也无法正常工作。导致“牵一发而动全身”的情况。 68 | 69 |
70 | 71 | #### 引入IOC的软件系统 72 | 73 | 在引入IOC容器后,我们再来看一下这个软件系统: 74 | 75 | ![容器托管后的对象](https://gitee.com/jyannis/doc/raw/master/JavaLearning/Spring/%E5%AE%B9%E5%99%A8%E6%89%98%E7%AE%A1%E5%90%8E%E7%9A%84%E5%AF%B9%E8%B1%A1.png) 76 | 77 | 齿轮之间的转动交给了“第三方”——也就是IOC容器。 78 | 79 | 由IOC容器来对各个对象进行统一的管理,当某个对象B需要对象A来帮助它完成什么功能时,就由IOC容器来把容器管理的A“借给”(专业术语叫“注入”)B来使用。 80 | 81 |
82 | 83 | #### 引入IOC的效果 84 | 85 | 对于这一点,我们不妨来看一下去掉IOC容器后的这张图: 86 | 87 | ![去除容器的状态](https://gitee.com/jyannis/doc/raw/master/JavaLearning/Spring/%E5%8E%BB%E9%99%A4%E5%AE%B9%E5%99%A8%E7%9A%84%E7%8A%B6%E6%80%81.png) 88 | 89 | 可以发现对象与对象之间不再紧密贴合。 90 | 91 | 这样的话,在我们编码实现A的时候,就无需考虑B、C和D了。 92 | 93 | 也即达到了我们想要的“**互相依赖的对象之间的解耦**”。 94 | 95 |
96 | 97 |
98 | 99 | ## 常见面试题 100 | 101 | 对于Spring IOC容器,最核心的两点就是: 102 | 103 | - 容器本身 104 | - 容器里的元素,或者说对象(bean) 105 | 106 | 围绕这两点,我们可以整理出下面三个非常重要的面试题。 107 | 108 |
109 | 110 | ### (1)Bean作用域 111 | 112 | Spring容器中的bean可以分为5个作用域: 113 | 114 | - singleton:默认,容器中只有一个bean的实例,由BeanFactory自身来维护。 115 | 116 | - prototype:为每一个bean请求提供一个实例。 117 | 118 | - request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。 119 | 120 | - session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。 121 | 122 | - global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。 123 | 124 |
125 | 126 | ### (2)Bean生命周期 127 | 128 | bean从出生到消亡,经历了哪些过程? 129 | 130 | 1. 实例化Bean: 131 | 132 | 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。 133 | 134 | 2. 设置对象属性(依赖注入): 135 | 136 | 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。 137 | 138 | 3. 处理Aware接口: 139 | 140 | 接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean: 141 | 1. 如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值; 142 | 2. 如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。 143 | 3. 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文; 144 | 145 | 4. BeanPostProcessor: 146 | 147 | 如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。 148 | 149 | 5. InitializingBean 与 init-method: 150 | 151 | 如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。 152 | 153 | 6. BeanPostProcessor 154 | 155 | 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术; 156 | 157 | > 以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。 158 | 159 | 7. DisposableBean: 160 | 161 | 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法; 162 | 163 | 8. destroy-method: 164 | 165 | 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。 166 | 167 |
168 | 169 | ### (3)BeanFactory和ApplicationContext的区别 170 | 171 | BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。 172 | 173 | - BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能: 174 | - 继承MessageSource,因此支持国际化。 175 | - 统一的资源文件访问方式。 176 | - 提供在监听器中注册bean的事件。 177 | - 同时加载多个配置文件。 178 | - 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。 179 | 180 |
181 | 182 | 其他的一些不同处: 183 | 184 | 先列个表格,然后下面详细说明。 185 | 186 | | | BeanFactory | ApplicationContext | 187 | | ------------------------------------ | ----------- | -------------------- | 188 | | **加载方式** | 惰性加载 | 预先加载 | 189 | | **启动速度** | 较快 | 较慢(加载Bean过多) | 190 | | **创建方式** | 编程式 | 编程式或声明式 | 191 | | **Bean(Factory)PostProcessor扩展点** | 手动注册 | 自动注册 | 192 | 193 |
194 | 195 | - 加载方式 196 | 197 | BeanFactroy采用的是惰性加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。 198 | 199 | ​ ApplicationContext是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。 200 | 201 | - 启动速度 202 | 203 | 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。 204 | 205 | - 创建方式 206 | 207 | BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。 208 | 209 | - 扩展点的注册 210 | 211 | BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。 212 | 213 |
214 | 215 |
216 | 217 | ## 核心源码梳理 218 | 219 | 在Spring IOC方面,最核心的源码也就是这三步: 220 | 221 | 1. bean解析 222 | 2. bean注册 223 | 3. bean加载 224 | 225 | 限于篇幅,这里仅对三段源码的核心逻辑做一个整理,这也是在我看来比较有用,但网上又很少有人去写的。 226 | 227 | 而至于真正要去深读源码的话,建议在网上找一些大牛的博客,或者阅读《Spring源码深度解析》这本书。 228 | 229 |
230 | 231 | ### (1)Bean解析 232 | 233 | 主要流程: 234 | 235 | 1. Spring载入Bean的XML配置文件 236 | 237 | 2. 把XML文件解析为Resource对象 238 | 239 | Resource接口抽象了所有Spring内部使用到的底层资源,例如File、URL、Classpath等等。 240 | 241 | 3. 加载XML得到对应的Document 242 | 243 | 4. 根据Document注册Bean 244 | 245 | XmlBeanDefinitionReader的registerBeanDefinitions方法 → DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法,其间有各种标签解析。有兴趣的读者可以去看看这一段的源码,是解析阶段里相对比较重要的源码 246 | 247 | 5. 解析并注册 248 | 249 | 解析完成的bean以BeanDefinition的形式注册到BeanDefinitionRegistry(就是我们的IOC容器)中,以map形式保存。 250 | 251 |
252 | 253 | 其中**BeanDefinition**是一个关键的点,需要注意一下: 254 | 255 | BeanDefinition是一个接口,在Spring中存在三种实现:RootBeanDefinition、ChildBeanDefinition以及GenericBeanDefinition。其中GenericBeanDefinition是xml中读到的bean信息第一步存储的形式,之后会转为RootBeanDefinition。 256 | 257 | - 父bean用RootBeanDefinition表示, 258 | 259 | - 子bean用ChildBeanDefinition表示, 260 | 261 | - 没有父bean的bean用RootBeanDefinition表示。 262 | 263 | - GenericBeanDefinition是一站式服务类。 264 | 265 | 什么叫一站式服务类?可以理解为就是过渡用的类,最后还是要转为RootBeanDefinition和ChildBeanDefinition的。 266 | 267 |
268 | 269 | ### (2)Bean注册 270 | 271 | 将beanDefinition直接放到map中,使用beanName作为key。 272 | 273 | 不过在真正注册之前,要做一些准备工作(了解即可): 274 | 275 | 1. 校验AbstractBeanDefinition的methodOverrides属性 276 | 2. 对beanName已经注册的情况处理(如果不允许beanName覆盖就抛出异常) 277 | 3. 加入map缓存 278 | 4. 清除解析之前留下的对应beanName的缓存 279 | 280 |
281 | 282 | ### (3)Bean加载 283 | 284 | 在AbstractBeanFactory的`doGetBean(final String name, @Nullable final Class requiredType,@Nullable final Object[] args, boolean typeCheckOnly)`中加载 285 | 286 | 1. 转换对应beanName为真实的beanName(转换别名为真正的beanName,如果是FactoryBean就去除开头的&符号) 287 | 288 | - 之所以如果是FactoryBean就要做处理,是因为bean的class属性实现类是FactoryBean时,通过getBean返回的不是FactoryBean本身,而是FactoryBean的getObject方法返回的对象 289 | 290 | - FactoryBean是为了隐藏实例化一些复杂bean的细节,给上层应用带来便利。FactoryBean使用了工厂模式 291 | 292 | 2. 尝试从缓存中加载单例 293 | 294 | 1. 进入方法`getSingleton(String beanName, boolean allowEarlyRefe rence)`且allowEarlyReference为true 295 | 1. 优先在**singletonObjects**找 296 | 2. 没有就在**earlySingletonObjects**找 297 | 3. 再没有就取出对应的**singletonFactories**.get(beanName).getObject(),并存到**earlySingletonObjects**里 298 | 299 | 2. 如果缓存中能找到 300 | 1. bean的实例化(对缓存中取出的原始状态bean进行加工) 301 | 3. 如果缓存中找不到 302 | 1. prototype模式的依赖检查(有循环依赖就抛出异常,只有单例情况才会尝试解决循环依赖) 303 | 2. 如果当前beanFactory中没有相应beanName,就尝试从parentFactory里检测、取出beanName对应bean 304 | 3. 将GenericBeanDefinition转换为RootBeanDefinition(这是因为之后对于bean的所有后续处理都是针对RootBeanDefinition进行的) 305 | 4. 寻找依赖,如果存在依赖就递归初始化依赖的bean 306 | 5. 针对不同的scope进行bean创建 307 | 6. 类型转换(例如返回的bean是个String,但requiredType传Integer) 308 | 309 | 310 |
311 | 312 | 其中2.1需要利用三级缓存解决循环依赖问题: 313 | 314 | (在DefaultSingletonBeanRegistry的`getSingleton(String beanName, boolean allowEarlyReference)`方法中) 315 | 316 | 涉及到的重点缓存map: 317 | 318 | **singletonObjects**:存放初始化好的bean(beanName → bean实例) 319 | 320 | **earlySingletonObjects**:存放刚实例化好的,但是还未配置属性和初始化的bean(提前曝光的单例对象) 321 | 322 | **singletonFactories**:存放beanName和创建bean的工厂之间的关系(beanName → ObjectFactory) 323 | 324 | 举例: 325 | 326 | 例如循环依赖A → B → A 327 | 328 | 1. 尝试getSingleton("A",true) 329 | 330 | singletonObjects里找不到,并且A并不在创建状态(`isSingletonCurrentlyInCreation(beanName)`为false),直接返回空(也就是缓存取不到) 331 | 332 | 缓存取不到就开始直接尝试加载,加载时要递归加载依赖的bean,所以接下来尝试加载B 333 | 334 | 2. 尝试getSingleton("B",true) 335 | 336 | singletonObjects里找不到,并且B并不在创建状态,直接返回空 337 | 338 | 开始尝试加载B依赖的bean,也就是A 339 | 340 | 3. 尝试getSingleton("A",true) 341 | 342 | singletonObjects里找不到,并且A在创建状态,所以去earlySingletonObjects里找 343 | 344 | 发现earlySingletonObjects里也没有,就从singletonFactories里拿,提前曝光到earlySingletonObjects里 345 | 346 | A成为一个还未初始化完全,但是可以被依赖的bean 347 | 348 | 4. B成功初始化 349 | 5. A成功初始化 350 | 351 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 本仓库适用对象: 2 | > 3 | > 1. 想要实践Java后台开发的新手 4 | > 2. 想要拿offer到手软的应届生、低工龄社会人 5 | 6 | 7 | 8 | 更多原创内容和干货分享: 9 | 10 | 1. [公众号—Jyannis](#公众号) : 每日面试题分享 + 第一手热乎乎面经及面试故事 11 | 12 | 13 | 14 |

15 | 微信群 16 | 公众号 17 | 投稿 18 | 投稿 19 |

20 | 21 | 22 | 23 | 24 | ## 目录 25 | 26 | - [我是零基础,怎么上手Java后台开发(SpringBoot)?](https://zhuanlan.zhihu.com/p/97958284) 27 | - [我刚学明白SpringBoot,怎么开展学习微服务?](https://github.com/jyannis/SpringCloud-Alibaba-Learning) 28 | - [Java技术栈学习书籍推荐](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483774&idx=1&sn=76f898602aa9af5913f4c9ea82fc0d20&chksm=9ffc6318a88bea0ed50d39255bc27366391c62d84b3bbda79772ba11cb740070e91dd7b50bec&token=1776113537&lang=zh_CN#rd) 29 | - Java 30 | - 常见Java基础面试题 31 | - 容器(集合框架) 32 | - JVM 33 | - [运行时数据区](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483776&idx=1&sn=25fbf98bb6dcda4e924675dc1e64997e&chksm=9ffc63e6a88beaf053d6036e5c4fff87af41df7d5a6e35329e115f9123b9b2d5c4363068f4c2&token=1776113537&lang=zh_CN#rd) 34 | - [垃圾回收](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483779&idx=1&sn=6ae9053df60475e7033ed6f17615b7e4&chksm=9ffc63e5a88beaf3f717c04274c087f7b38b8cdcb65ac20d165ee301a3c1dc28d10260b3cca1&token=1776113537&lang=zh_CN#rd) 35 | - [Java四种引用类型](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483783&idx=1&sn=c04e4ff81c62e1048f56931fb60eb845&chksm=9ffc63e1a88beaf763aa90e484fae5b9506c5c68224b657ab46d29aa51316f72ee71ba823ddf&token=1776113537&lang=zh_CN#rd) 36 | - [类加载机制](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483777&idx=1&sn=f330cb56e1bf6e0863792c6160299416&chksm=9ffc63e7a88beaf16eeb8a05a6081dfa0baf2306779d2761c480e65262eee776bb2f8c88de9d&token=1776113537&lang=zh_CN#rd) 37 | - [调优命令行工具](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483719&idx=1&sn=fc747d2e5b9b1928f312f1cb9a643f3b&chksm=9ffc6321a88bea37aacff8850cd60b9b4e7b1540850daf97dfe853d1d1a3b07d30fe6d3ed250&token=1776113537&lang=zh_CN#rd) 38 | - [Volatile关键字](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483765&idx=1&sn=cf0c1b74985e2389719d6ae1f51f4258&chksm=9ffc6313a88bea05f7c77c45247a5a1ce251b77b13c71c0068a4b30cc2effee2e80e21e59988&token=1776113537&lang=zh_CN#rd) 39 | - [JMM](https://github.com/jyannis/JavaLearning/blob/master/docs/JVM/JMM%20Java%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.md) 40 | - [JVM问题排查实战](#JVM问题排查实战) 41 | - 并发 42 | - Java与线程的基础知识 43 | - [Synchornized关键字](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483769&idx=1&sn=bdb296488a0b2b30ae98287ecd7151cf&chksm=9ffc631fa88bea09aed92f62d1c2a8b6d469ec918586fa4e011b72c304bcb8ae5541465de9d3&token=1776113537&lang=zh_CN#rd) 44 | - 锁优化 45 | - [同步组件](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483773&idx=1&sn=53c49298fae09a9e8bc089bf2cc0f046&chksm=9ffc631ba88bea0de0e26906c1bfb2e7234aafb8044a8d3d798c01457be77a57d57c0858c2cb&token=1776113537&lang=zh_CN#rd) 46 | - [线程池](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483763&idx=1&sn=73501f177d4882a23401e614b7a06b5d&chksm=9ffc6315a88bea030635a6fc138fe4d41b10a2801c04f7eefdbe642b0317d5a9de98e627edef&token=1776113537&lang=zh_CN#rd) 47 | - 并发容器 48 | - [ThreadLocal](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483891&idx=1&sn=7a6f3790a5fccd9cd378141215941a18&chksm=9ffc6395a88bea831a9b7c03fa50abefba84574f16f2c7c7186ce1e8097bf8f199847d041943&token=1533871073&lang=zh_CN#rd) 49 | - MySQL 50 | - [索引](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483844&idx=1&sn=d49ea52feb5efefc6f62fdb27bb2bc39&chksm=9ffc63a2a88beab497fc893b09657dd76bfe8135275cd01bca1e9ca5589c3b99de5f28a9f2ba&token=1776113537&lang=zh_CN#rd) 51 | - [锁](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483775&idx=1&sn=a9f52d630f191aa1afcf71e38e595800&chksm=9ffc6319a88bea0f8c37b39d1b9aeabb03b7f403e832729c4172115d30db91325bcf3ce57535&token=1776113537&lang=zh_CN#rd) 52 | - [Explain执行计划分析](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483863&idx=1&sn=9a25e58d37ee017688805fee46f59bb1&chksm=9ffc63b1a88beaa79269295884f3f4a275edd3879c3131784bd32255fe21e022e9470f986b9d&token=1776113537&lang=zh_CN#rd) 53 | - 分区 54 | - [InnoDB存储引擎体系结构](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483864&idx=1&sn=04ffd18b14d868acc72865bec6dd5e24&chksm=9ffc63bea88beaa8780b77edf6fed1bb72bc12c06d8919ffc5a6223f14dcd3e48665b59173f8&token=1776113537&lang=zh_CN#rd) 55 | - 文件 56 | - 查询处理流程 57 | - Redis 58 | - [概述](https://github.com/jyannis/JavaLearning/blob/master/docs/redis/概述.md) 59 | - [数据结构](https://github.com/jyannis/JavaLearning/blob/master/docs/redis/数据结构.md) 60 | - [持久化](https://github.com/jyannis/JavaLearning/blob/master/docs/redis/持久化.md) 61 | - [常见缓存问题及其解决](https://github.com/jyannis/JavaLearning/blob/master/docs/redis/常见缓存问题及其解决.md) 62 | - [常用命令](https://github.com/jyannis/JavaLearning/blob/master/docs/redis/常用命令.md) 63 | - Spring 64 | - [IOC](https://github.com/jyannis/JavaLearning/blob/master/docs/Spring/Spring%20IOC.md) 65 | - [AOP](https://github.com/jyannis/JavaLearning/blob/master/docs/Spring/Spring%20AOP.md) 66 | - 事务 67 | - [网络](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483809&idx=1&sn=ddf5933cf18ed6f5ba4e30a634f18da3&chksm=9ffc63c7a88bead1a8c91fa846a7ba7bacbf8faff04b6ec4d3f99909331a0a369de75d036006&token=1776113537&lang=zh_CN#rd) 68 | - 操作系统 69 | - 设计模式 70 | - [装饰者Decorator](https://github.com/jyannis/design-patterns/tree/master/Decorator) 71 | - [观察者Observer](https://github.com/jyannis/design-patterns/tree/master/Observer) 72 | - [策略Strategy](https://github.com/jyannis/design-patterns/tree/master/Strategy) 73 | - 算法 74 | - IO 75 | - [一文读懂BIO、NIO、AIO](https://github.com/jyannis/JavaLearning/blob/master/docs/IO/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82BIO%E3%80%81NIO%E3%80%81AIO.md) 76 | - [各种面经与面试故事](#面经与面试故事) 77 | - 面试技巧 78 | - [如何进行知识积累](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#如何进行知识积累) 79 | - [如何把握实践与理论的天平](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#如何把握实践与理论的天平) 80 | - [我应该如何整理笔记](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#我应该如何整理笔记) 81 | - [怎么复习才不会忘](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#怎么复习才不会忘) 82 | - [面试前](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#面试前) 83 | - [怎么写出让人眼前一亮的简历](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#怎么写出让人眼前一亮的简历) 84 | - [如何突击面试](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#如何突击面试) 85 | - [面试前焦虑该怎么办](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#面试前焦虑该怎么办) 86 | - [面试开始](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#面试开始) 87 | - [自我介绍到底要怎么说](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#自我介绍到底要怎么说) 88 | - [面试中](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#面试中) 89 | - [技术面试的时候应该注意些什么](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#技术面试的时候应该注意些什么) 90 | - [面试尬场怎么办](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#面试尬场怎么办) 91 | - [如何学会埋坑](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#如何学会埋坑) 92 | - [面试结束](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#面试结束) 93 | - [面试官:“你有什么想问我的吗”,该说什么?](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#面试官:"你有什么想问我的吗",该说什么?) 94 | - [我怎样才能知道我是否通过了](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md#我怎样才能知道我是否通过了) 95 | - 技术人生 96 | - [今天我收到了蚂蚁金服实习offer](https://github.com/jyannis/JavaLearning/blob/master/docs/tech-life/今天我收到了蚂蚁金服实习offer.md) 97 | 98 | 99 | 100 | 101 | 102 | ## JVM问题排查实战 103 | 104 | - [发生死锁如何排查](https://blog.csdn.net/m0_46311226/article/details/104970857) 105 | - [CPU过高如何排查](https://blog.csdn.net/m0_46311226/article/details/105010210) 106 | - [发生OOM如何排查](https://blog.csdn.net/m0_46311226/article/details/104996664) 107 | 108 | 109 | 110 | 111 | 112 | ## 面经与面试故事 113 | 114 | 面经:由jyannis一个字一个字地整理答案,供读者自测与复习。 115 | 116 | - 阿里 117 | - [阿里云一面 2020-03-11](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483795&idx=1&sn=68b8a8607863a8b57de367bfbafd325c&chksm=9ffc63f5a88beae3bf9026fb77174edd9814906a9330149b379ca5919a2f9d22a283ecaa17c6&token=1776113537&lang=zh_CN#rd) 118 | - [淘系部门一面 2020-03-14](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483850&idx=1&sn=b2323dc0690791f2efb8b7287d3d3218&chksm=9ffc63aca88beabae428a6682c84b9fc8861b89aeef3ccfa97474508a3f52fff68d59bdcc698&token=1776113537&lang=zh_CN#rd) 119 | - [淘系部门一面 2020-03-15](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483859&idx=1&sn=03f4b1b3a66a8d6ab671ac15d837109a&chksm=9ffc63b5a88beaa3ed51cd7da9e6ae74309ebc9d89a94ee733f4e63e7cbbec1ab4f608cc3f07&token=1776113537&lang=zh_CN#rd) 120 | 121 | 122 | 123 | 面试故事:jyannis身边的同学真实的面试经历 124 | 125 | - 阿里 126 | - [淘系部门三面](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483816&idx=1&sn=a0f040286dc5a54f7139421e802a8705&chksm=9ffc63cea88bead8974be27ed3fdb828d9439c5626f8bbb291d4aa2270329f5dd27a33ced115&token=1776113537&lang=zh_CN#rd) 127 | - 字节跳动 128 | - [中台部门一面](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483835&idx=1&sn=0e1e8f8f52e98e6fd84552ff168c6d2e&chksm=9ffc63dda88beacb563e078874f87c0c79cf6922d7eb36e3282cc426bd1a60e295b6674f4972&token=1776113537&lang=zh_CN#rd) 129 | - [中台部门二面](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483831&idx=1&sn=68ddf105230922302e3d7b051efe7c8a&chksm=9ffc63d1a88beac7facb9f1e1880075c45bc5e8764e6951ac8cb2eb2e0f15b7183d24610bf5a&token=1776113537&lang=zh_CN#rd) 130 | - 携程 131 | - [后台日常实习](https://mp.weixin.qq.com/s?__biz=MzA4Mjk5OTA0OQ==&mid=2247483836&idx=1&sn=3f47d42abb1a9ad074acf8b22f4145ca&chksm=9ffc63daa88beacce39f45211b36de0badf00c143d7f0d125ee61f0a025facc6d8f419142799&token=1776113537&lang=zh_CN#rd) 132 | 133 | 134 | 135 | 136 | 137 | ## 说明 138 | 139 | 开源项目在于大家的参与,这才使得它得以进步。点一下watch是对我最大的支持。 140 | 141 | 142 | 143 | ### 作者介绍 144 | 145 | ​ 我是来自华东师范大学软件工程专业的2017级本科生。 146 | 147 | ​ 于高三毕业(2017年7月)开始学习Java语言,在今年(2020,大三)先后斩获携程(1月)、字节跳动(三月)、蚂蚁金服(四月)实习生offer,并拿到最高的双A评级(本部门P9评A + 交叉面P9评A),大概率转正。 148 | 149 | ​ 作为一名踩过荆棘走过弯路的Java学习者,希望借助这篇开源文档对后来者提供一些帮助。 150 | 151 | 152 | 153 | ### 关于转载 154 | 155 | 如果你需要转载本仓库的一些文章到自己的博客的话,记得注明原文地址就可以了。 156 | 157 | ### 如何对该开源文档进行贡献 158 | 159 | 1. 笔记内容大多是手敲,所以难免会有笔误,你可以帮我找错别字。 160 | 2. 很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。 161 | 3. 现有的知识点难免存在不完善或者错误,所以你可以对已有知识点进行修改/补充。 162 | 163 | 164 | 165 | 166 | 167 | ### 联系我 168 | 169 | 疫情阶段,jyannis最近闲得慌,欢迎大家来打扰: 170 | 171 | 172 | 173 | 微信: 174 | 175 | 微信 176 | 177 | 178 | 179 | 180 | 181 | 扣扣: 182 | 183 | qq 184 | 185 | 186 | 187 | 188 | 189 | ### 公众号 190 | 191 | 欢迎关注jyannis,不定期分享第一手笔经面经,及最热最潮的技术知识。 192 | 193 | 你的每一次点击和在看都是对我最大的支持! 194 | 195 | ![Jyannis](https://gitee.com/jyannis/JavaLearning/raw/master/docs/Jyannis.jpg) 196 | 197 | -------------------------------------------------------------------------------- /docs/redis/常用命令.md: -------------------------------------------------------------------------------- 1 | 这里是一些Redis常用命令的整理,了解即可。 2 | 3 | **目录** 4 | 5 | - [字符串String](#字符串String) 6 | 7 | - [哈希Hash](#哈希Hash) 8 | 9 | - [列表List](#列表List) 10 | 11 | - [集合Set](#集合Set) 12 | 13 | - [有序集合Zset](#有序集合Zset) 14 | 15 | - [键管理](#键管理) 16 | - [单个键管理](#单个键管理) 17 | - [键迁移](#键迁移) 18 | - [渐进式键遍历](#渐进式键遍历) 19 | 20 | 21 | 22 | ## 字符串String 23 | 24 | | **命令** | **含义** | **备注** | 25 | | ---------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- | 26 | | set(nx/xx) {key} {value} | 设置值nx:key不存在才能设置成功xx:key必须存在才能设置成功 | setnx hello redis如果hello已存在就返回0不存在就返回OK | 27 | | setex {key} {seconds} {value} | 设置会在seconds秒后过期的值 | | 28 | | get {key} | 获取值 | | 29 | | mset {key} {value} {key} {value}…… | 批量设置值 | mset a 1 b 2 c 3 | 30 | | mget {key} {key} {key}…… | 批量获取值 | mget a b c d | 31 | | exists {key} | 判断key是否存在 | exists hello | 32 | | incr {key} | 对值做自增。值不是整数则返回错误;值是整数,返回自增后结果;key不存在,按照0自增,返回1。 | | 33 | | incrby {key} {increment} | 自增指定数字 | | 34 | 35 |
36 | 37 |
38 | 39 | ## 哈希Hash 40 | 41 | | **命令** | **含义** | **备注** | 42 | | --------------------------------------------- | --------------------------- | ---------------------------------- | 43 | | hset(nx) {key} {field} {value} | 设置值 | hset user:1 name tom | 44 | | hget {key} {field} | 获取值 | hget user:1 name | 45 | | hdel {key} {field} {field} …… | 批量删除field | hdel user:1 name | 46 | | hlen {key} | 计算field个数 | hlen user:1 | 47 | | hmset {key} {field} {value} {field} {value}…… | 批量设置值 | | 48 | | hmget key {field} {field}…… | 批量获取值 | | 49 | | hexists {key} {field} | 判断field是否存在 | hexists user:1 name | 50 | | hkeys {key} | 获取所有field | hkey user:1 | 51 | | hvals {key} | 获取所有value | | 52 | | hgetall {key} | 获取所有field和所有value | 最好用hscan渐进式遍历来替代hgetall | 53 | | hincrby {key} {field} | 和incrby一样,只是针对field | | 54 | 55 |
56 | 57 |
58 | 59 | ## 列表List 60 | 61 | | **命令** | **含义** | **备注** | 62 | | ------------------------------------------- | ------------------------------------------------------------ | -------------------------------------------- | 63 | | rpush {key} {value} {value}…… | 从右边插入元素 | rpush listkey c b a | 64 | | lpush | 同上。从左边插入元素 | | 65 | | linsert {key} before\|after {pivot} {value} | 找到列表中等于pivot的元素,在其前或后插入元素value | linsert listkey before b java | 66 | | lrange {key} {start} {end} | 获取指定范围内的元素列表(双闭区间)下标从左到右0~N-1;下标从右到左-1~-N | lrange listkey 0 2 | 67 | | lindex {key} {index} | 获取列表指定索引下标的元素index=-1即为最后一个元素 | lindex listkey 1 | 68 | | llen {key} | 获取列表长度 | | 69 | | lpop {key} | 从左侧弹出元素 | | 70 | | lrem {key} {count} {value} | 从列表中找到等于value的元素并删除,根据count分为三种情况:count>0:从左到右删除最多count个元素;count<0:从右到左删除最多count绝对值个元素count=0:删除所有 | lrem listkey 4 a | 71 | | ltrim {key} {start} {end} | 按照索引范围修剪列表(只保留start~end) | ltrim listkey 1 3只保留第2个到第4个元素 | 72 | | lset {key} {index} {newValue} | 将列表中下标为index的元素设置为newValue | lset listkey 2 python | 73 | | blpop {key} {key}……{timeout} | 如果列表为空,客户端等到timeout秒后返回;如果列表不为空,客户端立即返回; | blpop list:test 3lpush+brpop可以实现阻塞队列 | 74 | 75 |
76 | 77 |
78 | 79 | ## 集合Set 80 | 81 | | **命令** | **含义** | **备注** | 82 | | ------------------------------------- | ------------------------------ | ------------------------------------------------------------ | 83 | | sadd {key} {element} {element}…… | 添加元素到集合key中 | sadd myset a b c | 84 | | srem {key} {element} {element}…… | 删除元素 | | 85 | | scard {key} | 计算元素个数 | | 86 | | sismember {key} {element} | 判断元素element是否在集合key中 | 在就返回1,不在返回0 | 87 | | srandmember {key} [count] | 随机从集合返回指定个数元素 | count是可选参数,不写默认1 | 88 | | spop {key} | 从集合随机弹出元素 | | 89 | | smembers {key} | 获取全部元素 | 返回结果是无序的可以考虑用sscan来代替 | 90 | | sinter {key} {key}…… | 求多个集合的交集 | | 91 | | sunion {key} {key}…… | 求多个集合的并集 | | 92 | | sdiff {key} {key}…… | 求多个集合的差集 | | 93 | | sinterstore {destination key} [key……] | 将交集、并集、差集的结果保存 | 存在destination key中sinterstore user:1_2:inter user:1:follow user:2:follow | 94 | 95 |
96 | 97 |
98 | 99 | ## 有序集合Zset 100 | 101 | | **命令** | **含义** | **备注** | 102 | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | 103 | | zadd {key} {score} {member} {score} {member}…… | 添加元素到集合key中 | sadd user:ranking 251 tom | 104 | | zrem {key} {member} {member}…… | 删除元素 | | 105 | | zcard {key} | 计算元素个数 | | 106 | | zscore {key} {member} | 计算某个成员的分数 | | 107 | | zrank {key} {member} | 按分数从低到高返回member在key中的排名(从0开始计算) | zrank替换为zrevrank表示从高到低 | 108 | | zincrby {key} {increment} {member} | 给成员member增加increment分数 | zincrby user:ranking 9 tom | 109 | | zrange {key} {start} {end} [withscores] | 返回指定排名范围的成员,从低到高返回。withscores可选参数,加上就会同时返回成员分数 | zrange替换为zrevrange表示从高到低返回 | 110 | | zcount {key} {min} {max} | 返回指定分数范围的成员个数 | zcount user:ranking 221 200 | 111 | | zremrangebyrank {key} {start} {end} | 删除指定排名内的升序元素 | zremrangebyrank user:ranking 0 2 | 112 | | zremrangebyscore {key} {min} {max} | 删除指定分数范围的成员 | zremrangebyscore user:ranking 250 +inf删除250分以上的成员返回结果为成功删除的个数 | 113 | | zinterstore {destination key} {numkeys} {key……} [weight……] [aggregate sum\|min\|max] | 做交集。(并集类似) | {key……}:需要做交集计算的key[weight……]:每个key的权重,默认1[aggregate sum\|min\|max]:计算成员交集后,分值score可以按照求和、最小值、最大值进行汇总,默认sum | 114 | 115 |
116 | 117 |
118 | 119 | ## 键管理 120 | 121 | #### 单个键管理 122 | 123 | | **命令** | **含义** | **备注** | 124 | | -------------------------- | ----------------------------- | ------------------------------------------------------------ | 125 | | rename {key} {newkey} | 重命名键 | 如果在rename之前,newkey已经存在,那么它的值会被覆盖。为了防止强行rename,可以使用renamenx。重命名时会del旧键,小心阻塞(旧值太大) | 126 | | randomkey | 随机返回一个键 | | 127 | | expire {key} {seconds} | 键在seconds秒后过期 | expire改pexpire可以把秒级改为毫秒级。如果key不存在,返回0;如果过期时间为负,键立刻删除。 | 128 | | expireat {key} {timestamp} | 键在秒级时间戳timestamp后过期 | expireat改pexpireat可以把秒级改为毫秒级 | 129 | | persist {key} | 删除过期时间(设为永久) | | 130 | | ttl {key} | 查看key的过期时间 | 返回-1:没有设置过期时间;返回-2:键不存在。ttl改pttl可以把秒级改为毫秒级。 | 131 | 132 | redis不支持二级数据结构(例如hash,list)内部元素的过期功能 133 | 134 |
135 | 136 | #### 键迁移 137 | 138 | **move** 139 | 140 | `move key db` 141 | 142 | move用于在Redis内部多个数据库之间进行数据迁移,一般用不到。 143 | 144 | **dump + restore** 145 | 146 | `dump key` 147 | 148 | `restore key ttl value`(ttl代表过期时间) 149 | 150 | dump + restore可以实现在不同Redis实例之间进行数据迁移的功能,迁移过程分为两步: 151 | 152 | 1. 在源Redis,dump命令将键值序列化,格式采用RDB; 153 | 2. 在目标Redis,restore命令将上面序列化的键值复原,其中ttl参数代表过期时间,ttl=0代表不过期。 154 | 155 | 注意: 156 | 157 | - 迁移过程并非原子性的,而是通过客户端分布完成。 158 | - 迁移过程是开启了两个客户端连接,所以dump结果不是在源Redis和目标Redis之间传输。(通过应用程序等) 159 | 160 | 伪代码示例: 161 | 162 | ```java 163 | Redis sourceRedis = new Redis("sourceMachine",6379); 164 | Redis targetRedis = new Redis("targetMachine",6379); 165 | targetRedis.restore("keyName",0,sourceRedis.dump(key)); 166 | ``` 167 | 168 | **migrate** 169 | 170 | - migrate是将dump、restore、del三个命令进行组合,简化操作流程。 171 | - migrate具有原子性。 172 | - migrate对于Redis Cluster水平扩容起到重要作用。 173 | 174 | **三种迁移命令的比较** 175 | 176 | | **命令** | **作用域** | **原子性** | **支持多个键** | 177 | | -------------- | --------------------------- | ---------- | -------------- | 178 | | move | Redis实例内部(不同库之间) | ✔ | ✘ | 179 | | dump + restore | Redis实例之间 | ✘ | ✘ | 180 | | migrate | Redis实例之间 | ✔ | ✔ | 181 | 182 |
183 | 184 | #### 渐进式键遍历 185 | 186 | 使用**scan**命令: 187 | 188 | **scan cursor [match pattern] [count number]** 189 | 190 | - cursor 191 | 192 | - - 游标。 193 | - 第一次遍历从0开始,每次scan返回当前游标值 194 | - 直到返回的游标值为0,表示遍历结束 195 | 196 | - match pattern 197 | 198 | - - 做模式的匹配 199 | 200 | - count number 201 | 202 | - - 表明每次要遍历的键个数 203 | - 默认为10 204 | 205 | **scan可以解决keys命令可能产生的阻塞问题** 206 | 207 | 如果在scan过程中有键的变化(增删改)可能导致遍历出现问题(新键没遍历到,重复遍历等) -------------------------------------------------------------------------------- /docs/tech-life/今天我收到了蚂蚁金服实习offer.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ​ 在今天,我收获了蚂蚁金服A评级的实习录用offer。从开始面试到拿到口头offer(4x技术面+1xHR面)战线大约有半个月,从拿到口头offer到收到正式录用邮件大概又是半个月。 4 | 5 | ​ 思前想后,决定还是为自己做一个整理与总结。一方面是回顾并记录自己的努力过程,一方面也是希望对后来者起到一些帮助。 6 | 7 | ​ 前方高能预警,本篇文章要通读可能要很久。 8 | 9 | ​ 以下内容普适于**技术岗**,非技术岗请部分参考。 10 | 11 | 12 | 13 | 目录 14 | 15 | - [如何进行知识积累](#如何进行知识积累) 16 | - [如何把握实践与理论的天平](#如何把握实践与理论的天平) 17 | - [我应该如何整理笔记](#我应该如何整理笔记) 18 | - [怎么复习才不会忘](#怎么复习才不会忘) 19 | - [面试前](#面试前) 20 | - [怎么写出让人眼前一亮的简历](#怎么写出让人眼前一亮的简历) 21 | - [如何突击面试](#如何突击面试) 22 | - [面试前焦虑该怎么办](#面试前焦虑该怎么办) 23 | - [面试开始](#面试开始) 24 | - [自我介绍到底要怎么说](#自我介绍到底要怎么说) 25 | - [面试中](#面试中) 26 | - [技术面试的时候应该注意些什么](#技术面试的时候应该注意些什么) 27 | - [面试尬场怎么办](#面试尬场怎么办) 28 | - [如何学会埋坑](#如何学会埋坑) 29 | - [面试结束](#面试结束) 30 | - [面试官:“你有什么想问我的吗”,该说什么?](#面试官:"你有什么想问我的吗",该说什么?) 31 | - [我怎样才能知道我是否通过了](#我怎样才能知道我是否通过了) 32 | - [我两年半的技术人生](#我两年半的技术人生) 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ## 如何进行知识积累 41 | 42 | ​ 在讲面试技巧之前,显然知识积累是不可或缺的。不然即使面试侥幸通过,在之后的工作中也会体验极差。 43 | 44 | ### 如何把握实践与理论的天平 45 | 46 | ​ 实践是什么? 47 | 48 | - 比如你`new Object()`初始化了一个对象来使用; 49 | 50 | - 比如你用HashMap结构作为容器存储了一些数据; 51 | 52 | - 比如你拿SpringBoot搭建了一个web网站后台; 53 | 54 | - 比如…… 55 | 56 | 57 | 58 | ​ 理论是什么? 59 | 60 | - 初始化对象有什么代价?背后的底层实现逻辑是怎么样的?初始化太多对象可能导致什么问题?为什么我经常初始化对象,明明是空间消耗大,反而导致程序在运行时间上变得缓慢? 61 | - HashMap适合什么场景?我现在这个场景真的是用HashMap最合适吗?运用的时候有没有什么需要注意的?有时候遇到一些特殊需求,在HashMap基础上可以再优化性能吗? 62 | - 天天用SpringBoot,IOC、AOP到底是什么概念,背后怎么实现的?它们适用场景如何,我的用法是最佳实践吗?会不会有什么弊端,导致在生产环境出现问题? 63 | - …… 64 | 65 | ​ 66 | 67 | ​ 实践与理论的学习,到底应该侧重实践一些呢,还是侧重理论一些呢? 68 | 69 | ​ 都说**实践与理论要两相结合,缺一不可**,在我看来这是一句废话。重点是如何权衡两者,并且在不同的发展阶段,两者的**侧重比例是否又要发生变化**呢? 70 | 71 | ​ 在我看来,实践**决定方向**,理论**填补细节**。 72 | 73 | 74 | 75 | #### 1、首先要实践,然后填补理论 76 | 77 | ​ 程序员**首先要实践**,才能够收获基本的技术视野和处理问题的能力,这两者都是不可或缺的。 78 | 79 | ​ 有了基本的视野之后,就可以根据自己学习的方向,去**填补你的细节**,例如: 80 | 81 | 1. 我学习了Java,会写一些应用程序了,也知道如何利用应用服务层的Java来对数据库层的数据做一些处理,那么接下来: 82 | 1. 对数据怎么处理会更快(这里可能就会引出多线程,然后就可能引出线程池,又引出JDK提供的线程池有什么并发问题,怎么解决,然后可能又引入并发包,一下子串出好多) 83 | 2. 各种优化(比如初始化太多对象导致频繁GC,全局变量太多导致占存一直很高blabla) 84 | 2. 我学习了MySQL,会用SQL语句操作数据了,知道建索引可以加快访问速度了,那么接下来: 85 | 1. 在SQL语句上是不是也可以做一些优化来提升性能(比如很有名的**延迟关联**) 86 | 2. 索引到底应该怎么建才好(这就涉及到索引的很多知识,比如B+树,比如一些匹配原则例如**最左适配原则**等等) 87 | 88 |
89 | 90 | ​ 这种细节的填补是有**逻辑性**的,从我上面举的两个例子就可以看得出来。这种**逻辑性**的存在会使你在学习的过程中不会感到虚浮,会发现这些理论都是切实有用且有价值的。 91 | 92 | ​ 但这样的理论学习还不够,因为它**不系统**。充分的发散思维使你能够在实践之外追求到很多理论知识,但那些你发散不到的部分就接触不到了。这个时候你就需要系统性的学习,例如**读书**。 93 | 94 | ​ 当你第一次学会实践,就好像在自己的脑海中制作了一个**知识星球**。它里面几乎是空白的,但是球体本身存在,帮助你去界定、区分知识——借助这个球体的框架,当你在遇到一个新知识的时候,你会它有一个模糊的概念:这个知识到底是有用知识还是无用知识,它又在哪个范畴或层面里? 95 | 96 | ​ 所以借助了一开始的实践,然后你再去逻辑地发散、或者系统地读书的时候, 就不会让新知识成为无根之萍,而是切实地进入你的知识星球中,成为你**知识架构**的一部分。 97 | 98 |
99 | 100 | #### 2、理论再返回到实践中 101 | 102 | ​ 知识架构建立起来了,但它没有经历实践的检验,就依然是**不可用**的。 103 | 104 | ​ 一方面在于我们学习的理论知识可能是**过时**的,甚至是**谬误**的;另一方面我们可能以为自己懂了但其实没懂,这一点相信大家都能理解。 105 | 106 | ​ 那么如何实践呢?有些理论可能是很难实践出来的,但是至少我们要对能实践的一部分去做一下尝试。就比如简单的JVM排查和调优,通过MySQL EXPLAIN去检查执行计划并实践优化等等。 107 | 108 |
109 | 110 |
111 | 112 | ### 我应该如何整理笔记 113 | 114 | 首先要强调**笔记**是很重要的。至少我认识的那些能够面试进大厂的同学,每个人都会为自己做笔记。 115 | 116 | 而至于到底应该如何**整理**笔记,我认为最重要的有两点:一是**分门别类**,二是**控制粒度**。 117 | 118 | 119 | 120 | 首先是**分门别类**: 121 | 122 | 我们一定要能够清晰地把我们要整理的知识模块化,比如说JVM基础,我们可以这样分模块: 123 | 124 | - 运行时数据区 125 | - 垃圾回收 126 | - 类加载 127 | - JMM 128 | - JVM调优 129 | 130 | 然后分别根据每一个小模块,单独整理一篇笔记。 131 | 132 | 并且在篇末,或者另开一篇笔记,专门记录针对这个模块的比较**大而广的问题**。 133 | 134 | 例如运行时数据区,我们就可以记录这样一些问题: 135 | 136 | - 讲讲JVM运行时数据区的各个组成,是什么,有什么用 137 | - 讲讲对象。它是如何创建的,里面包含一些什么信息,如何定位到对象 138 | 139 | 记录这种大的问题,有助于我们进行**自测**。不要问自己太多细小的问题,除非你切实觉得它很重要。什么叫细小的问题呢?比如:**类里静态的基本数据类型存在方法区还是堆里?**这种问题枚举你是枚举不完的,它其实已经包含在了”**讲讲JVM运行时数据区的各个组成,是什么,有什么用**“这个问题里。 140 | 141 | 142 | 143 | 其次是**控制粒度**: 144 | 145 | 什么叫控制粒度?其实就是控制你笔记记录的详细程度。 146 | 147 | 如果你笔记中对于知识点的描述非常简洁,带来的好处是阅读起来就会很快,坏处是可能在阅读时导致你忽略掉一些本不该忽略的细节,或者甚至是:”咦,我这记的是啥,我怎么看不懂了。“ 148 | 149 | 那如果记得太详细呢?很显然,就会导致阅读起来非常繁琐,可能达不到一个理想的**迅速复习**的效果。 150 | 151 | 那到底应该控制在一个什么程度上呢? 152 | 153 | 我认为一篇良好的笔记应该满足以下两个条件: 154 | 155 | 1. 这段笔记切实提到了所有应该提到的知识点,不需要我去联想; 156 | 157 | 2. 这段笔记对于这些知识点都有简单的描述性文字,并且能够言简意赅,尽可能以列表列举的形式,不要有叙述性的内容。 158 | 159 | tips:什么叫叙述性内容? 160 | 161 | 举个例子,比如下面这是一段对于垃圾回收算法里的标记-清除算法的笔记: 162 | 163 | > 标记-清除算法: 164 | > 165 | > ​ 标记出所有需要回收的对象,根据标记统一回收。 166 | > 167 | > ​ 问题:1. 低效率 168 | > 169 | > ​ 2. 产生大量不连续内存碎片 170 | 171 | 带有叙述性内容的写法是怎么样的呢: 172 | 173 | > 标记-清除算法: 174 | > 175 | > ​ 它的基本原理是,标记出所有需要回收的对象,然后根据标记统一回收。 176 | > 177 | > ​ 它有两个问题,一是在于效率比较低,二是在于可能产生大量不连续的内存碎片。 178 | 179 | 非常显然,阅读前者的效率远高于后者。 180 | 181 | 整理笔记时,我们尽可能省去不必要的铺垫,例如”它的基本原理是“这句话就是完全不必要的。我们也可以尽可能省去一些联结词,例如”首先“、”然后“、”最后“这种。以及对于一些枚举性内容,我们尽可能采取列表而非文字形式表达,会更加直观易懂易记。 182 | 183 |
184 | 185 |
186 | 187 | ### 怎么复习才不会忘 188 | 189 | 每天都反复复习当然可以保证你不会忘,但会非常疲惫而且费时。 190 | 191 | 严格遵守网上的某些号称贴合人类记忆曲线的复习方式,坚持起来压力很大,最终往往也很难达到自己理想的效果。 192 | 193 | 实际上这种所谓”曲线“是有一定道理的,但是也要结合我们每个人自身的特殊情况,去找到最适合自己的一种记忆方式。 194 | 195 | 我认为这样一种记忆法是比较有效的: 196 | 197 | 从第一次复习并记忆的时候开始算,隔比较短的一段时间再复习并记忆一次,隔不太短的一段时间再复习一次,然后坚持每隔较长的一段时间就复习一次。 198 | 199 | 那么这个”**比较短的一段时间**“、”**不太短的一段时间**“、“**较长的一段时间**”又分别是多久呢?我们可以自己来决定。但需要知道的是,我们的复习间隔时间应该是不断变长的,直到一个比较稳定的值。 200 | 201 | 比如我个人的话,就喜欢第一次记忆完后,两小时再复习一次,隔一天再复习一次,然后隔四五天复习一次,之后可能就一直是7-10天会复习一次。 202 | 203 | 无需刻意,比如我本来打算三天后复习一次,但是三天后突然有什么事耽搁了。所以我提前到前一天,或者滞后到后一天,又能怎么样呢?记忆是量变的过程,一天两天的偏移根本不会有什么质变可言。 204 | 205 | 但让记忆周期大抵符合一个慢慢变长的规律,我想是有必要的。而如果你觉得最后每隔7-10天复习一次都会压力很大,那你也可以半个月甚至二十天才看一次。不过这样的话,等面试要来临的时候,你可能就难免需要简单突击一下了。 206 | 207 |
208 | 209 |
210 | 211 |
212 | 213 | ## 面试前 214 | 215 | ### 怎么写出让人眼前一亮的简历 216 | 217 | 按照一般流程来说,你的简历会先给懂一点技术/不懂技术的HR初审,然后再交给研发的同事过审,经过这两层都ok了,才会给你进面试的流程。 218 | 219 | 也就是说,你的简历不仅要让专业人士看着厉害,还要让非专业人士看着也厉害。 220 | 221 | 那么作为技术人员,简历到底应该怎么写呢? 222 | 223 | 224 | 225 | #### 1、综述 226 | 227 | 我们不妨**先概括后具体**,先总体看一下简历应该写一些什么东西,写多长篇幅,排版成什么样子,然后再分析每个模块应该如何去写。 228 | 229 | ##### 写什么东西 230 | 231 | 一个正常的简历应该有如下内容: 232 | 233 | - 基本信息及联系方式 234 | - 学习经历 235 | - 工作经历(或实习经历) 236 | - 项目经历 237 | - 专业技能 238 | - 自我评价(这个不一定要有,看前面篇幅) 239 | 240 | ##### 写多长篇幅 241 | 242 | 作为技术人员,我觉得简历1-2页就好,3页未免太过冗长。 243 | 244 | ##### 排版成什么样子 245 | 246 | 首先格式上来说,现在一般都是网申,也就是提交电子材料,所以HTML或者PDF格式为佳。 247 | 248 | 具体排版的话,尽可能简洁明了,不要花里胡哨。技术人,又不是去做UI设计,搞那么好看,人家也不会欣赏,反而觉得你不务正业。我个人建议简历的好看程度,达到markdown能支持的极限就可以了,不要超过markdown的能力。 249 | 250 |
251 | 252 | #### 2、基本信息及联系方式 253 | 254 | 可能会包括以下一些信息: 255 | 256 | - 姓名,电话,邮箱:这几个是必须的,不多说了。 257 | - 求职意向:单独列这条出来,我建议要写。不然你本来做后端的,人家给你安排到测试开发,你哭都来不及。 258 | - 个人网站/博客/GitHub:如果有厉害的一定要记得写,如果很水的话建议别写了。 259 | 260 |
261 | 262 | #### 3、学习经历 263 | 264 | 可能会包括以下一些信息: 265 | 266 | - 学历(或者毕业年份) 267 | - 绩点:如果不好看,请不要写,不要写,不要写 268 | - 专业:如果不是计算机相关专业,请不要写,不要写,不要写 269 | - 奖项:尽可能多写,从高到低排。先写厉害的,最后写不厉害的。如果没什么拿的出手的奖项,建议干脆什么也不要写。不要孤零零写个什么XX大学优秀团员上去,还不如不写。 270 | 271 |
272 | 273 | #### 4、工作经历(或实习经历) 274 | 275 | 这里就如实说就好,切记一定要准确,包括在职时间也写明白,一两个月的偏差也是在给自己埋炸弹。 276 | 277 | 应该包括以下信息: 278 | 279 | - 在职时间:例如2018.2~2019.3 280 | - 工作单位:如果不是大家熟知的大厂,最好附上行业 281 | - 部门:这个是可选项,不一定要写。如果是厉害的部门请记得写,比如腾讯微信 282 | - 岗位:建议具体。如果公司里岗位就叫“研发工程师”,你可以写成“JAVA研发工程师”或者“GO研发工程师”之类的。 283 | 284 |
285 | 286 | #### 5、项目经历 287 | 288 | 这是第二重要的部分!!第一重要的部分是下面的专业能力。但这也是决定你是否能简历过审的很重要的一部分,并且是可能导致面试官疯狂追问你的来源。 289 | 290 | 一般写2-3个项目经历比较好,相对来说,要挑最厉害的、最对口的、最近的。 291 | 292 | 那么每个项目经历中,应该写一些什么呢? 293 | 294 | - 项目名称 295 | 296 | - 项目简介:1-2句话就好,让别人知道你这是个什么项目。比如是个后台管理系统?比如是个电商平台?之类的 297 | 298 | - 个人技术工作:请以列表形式列举自己的核心工作,不要大段叙述性文字!! 299 | 300 | 301 | 302 | 后面是一些可选项,不一定要写: 303 | 304 | - 技术关键词:项目中涉及到的技术关键词 305 | 306 | - 项目中的收获 307 | 308 | - 项目成果:项目上线后有什么成果。比如抗住了多大的QPS,比如做到了多大的规模等等。请注意这两者是有区别的,前者可能更强调峰值,后者是长期的稳定性。当然还可以有其他的,但最好要有技术视角。 309 | 310 | 311 | 312 | 给一个优秀的范例: 313 | 314 | ![简历-项目经历-范例1](https://gitee.com/jyannis/JavaLearning/raw/master/docs/tech-life/200412-简历-项目经历-范例1.png) 315 | 316 | 317 | 318 | 如果觉得这个有点太高大上了,再看一个普通一点的: 319 | 320 | ![简历-项目经历-范例2](https://gitee.com/jyannis/JavaLearning/raw/master/docs/tech-life/200412-简历-项目经历-范例2.png) 321 | 322 |
323 | 324 | #### 6、专业技能 325 | 326 | 专业技能方面应该尽可能讲得全面,把自己确定会的一个都不要漏掉。 327 | 328 | 但是一定要和岗位对口。比如你投个Java后台,你非得写个摄影技能上去,反正我个人感觉不是很好。 329 | 330 | 如果你不知道怎么写,可以看一下岗位描述。一般投递简历前你看到的岗位描述上都会有对于技术要求的说明,比如: 331 | 332 | > =======我们的要求======= 333 | > 334 | > 1. 全日制211本科以上学历,计算机相关专业,毕业时间2020.11-2021.10 335 | > 2. JAVA基础扎实,理解io、多线程、集合等基础框架,对JVM原理有一定的理解 336 | > 3. 熟悉分布式、缓存、消息、搜索、推荐等技术,并能合理应用,解决实际问题 337 | > 4. 学习能力强,对代码质量及系统性能具备精益求精的精神 338 | > 5. 良好的团队沟通协同能力,抗压能力,勇于接受挑战 339 | > 6. 有作品或者实习经验优先 340 | 341 | 我们可以根据岗位的要求,结合我们自身的优势,来写我们的专业技能。像这样: 342 | 343 | ![简历-专业技能](https://gitee.com/jyannis/JavaLearning/raw/master/docs/tech-life/200412-简历-专业技能.png) 344 | 345 |
346 | 347 |
348 | 349 | ### 如何突击面试 350 | 351 | 我个人觉得,突击面试是在面试前1-10天的这个阶段。 352 | 353 | #### 1、笔记突击复习 354 | 355 | ​ 这个是毋庸置疑的。之前在[怎么复习才不会忘](#怎么复习才不会忘)里说,最后坚持每7-10天复习一次。如果你坚持了这种频率,我觉得笔记突击复习对你来说几乎是不需要的。不过如果你觉得心有不安,可以在突击阶段每天复习一次。 356 | 357 | ​ 切记一点,笔记突击复习时,请记得自己问自己问题,然后模拟一下在面试官面前你会怎么回答。别只是记忆,你还要学会如何表达。 358 | 359 | 360 | 361 | #### 2、针对性复习 362 | 363 | ​ 针对性复习包含两方面:**把握公司面试特点**+**刷特定面经**。 364 | 365 | ​ 首先是把握公司特点。打个比方,如果你去面试字节跳动,可能就需要多准备一点算法、网络,他们很喜欢问这些。如果你去面试美团,你可能就更需要准备一些JUC并发包、框架。 366 | 367 | ​ 把握公司特点很重要,你会发现即使同样是面试JAVA岗位,不同公司的面试内容差别也会很大。 368 | 369 | ​ 甚至不只是公司。到部门层面,差别也会很大。某公司A部门的JAVA岗,和该公司B部门的JAVA岗,就是要求不一样的。也许一个更注重并发能力,一个更注重排查调优能力等等。这一点你可以从岗位要求里去了解,或者问部门里相关的人员。 370 | 371 | 372 | 373 | ​ 其次是刷特定的面经。现在网上有很多人分享面经。可能不一定能直接找到你现在面试的部门的面经,但你至少可以找到同一家公司下、业务类型相近的面经。把他们题目拿过来,给自己模拟面试一遍。和前面说笔记突击复习一样,模拟你的表达,别只是心里想一想答案就过去了。 374 | 375 | 376 | 377 | #### 3、每日一道算法 378 | 379 | ​ 如果你面试中可能会出现算法题,建议你在突击阶段每天一道LeetCode以保持手感。不过这因人而异,有些人即使很久不做算法,临阵磨枪也不虚。 380 | 381 |
382 | 383 |
384 | 385 | ### 面试前焦虑该怎么办 386 | 387 | ​ 什么深吸一口气?站在阳台看看窗外?我觉得效果不是很大。 388 | 389 | ​ 深呼吸在面试前半小时是有效的,它可以缓解你面试前过分紧张的情绪。但它是缓解紧张的,无法缓解焦虑。 390 | 391 | ​ 焦虑的源头在于:**害怕意外的发生**。我觉得缓解焦虑最有效的方式就是,为自己**规划好一切**。 392 | 393 | ​ 在突击面试之前,时间尚早,相信你也不会有太多焦虑。当突击面试阶段开始,你就要开始学会为自己规划。让一切有条不紊地进行,你会发现你不再会为面试和其结果感到焦虑,因为你知道你自己做到了最好。 394 | 395 | ​ 那如何规划呢? 396 | 397 | ​ 不妨给个例子: 398 | 399 | ​ 比如我在下周六(2020-04/18)是阿里的一面。而今天是周日(2020-04-12),接下来我怎么安排呢? 400 | 401 | > 2020-04-12 周日 402 | > 403 | > ​ 复习单线程容器、JUC包,整理出相关的大而广的面试问题,进行自测。 404 | > 405 | > 2020-04-13 周一 406 | > 407 | > ​ 复习网络、操作系统 408 | > 409 | > 2020-04-14 周二 410 | > 411 | > ​ 复习JAVA常见面试题、JVM 412 | > 413 | > 2020-04-15 周三 414 | > 415 | > ​ 复习MySQL和Redis 416 | > 417 | > 2020-04-16 周四 418 | > 419 | > ​ 复习Spring、IO、设计模式 420 | > 421 | > 2020-04-17 周五 422 | > 423 | > ​ 网上查阅面经自测,做两道算法恢复一下手感 424 | > 425 | > 2020-04-18 周六 426 | > 427 | > ​ 盯着自己准备好的各种大而广的问题反复车轮式复习,直到面试前半小时。 428 | > 429 | > ​ 半小时里再背一遍自我介绍,复习一下简历里的项目。 430 | > 431 | > ​ 迎接面试。 432 | 433 | ​ 434 | 435 | ​ 如果这样规划以后,依然让你感到十分焦虑,很简单:把规划**再细化**。 436 | 437 | ​ 你会发现,当规划的细致程度达到某种阶段以后,你的焦虑会转化成压力和动力,不会再让你手足无措。 438 | 439 | 440 | 441 | ​ 最后说一个小tip。其实让你面试前不焦虑还有一个非常简单的办法,就是多投递简历,多面试,面多了你就~~会感到麻木,~~不会焦虑了。 442 | 443 |
444 | 445 |
446 | 447 |
448 | 449 | ## 面试开始 450 | 451 | ### 自我介绍到底要怎么说 452 | 453 | ​ 如果是一面/二面,一般是纯技术面,这个时候我建议可以遵照这样一种自我介绍模板: 454 | 455 | - 自报家门 456 | - 介绍自己的技术方向和技术能力 457 | - 我主攻的方向是XX,从技术上来说,我对于XX、XX(枚举一些技术点)比较熟悉(以引导面试官问你熟悉的内容) 458 | - 介绍项目经历(什么项目,在里面做什么事情,有什么难点,怎么克服的,有什么收获) 459 | - 我在之前XX(读大学?在XX工作?之类的)的时候,很喜欢并善于研究 / 做过一些有价值的项目,比如blabla。期间遇到过一个问题至今印象深刻,blabla。经过这个项目,我吸收了一些blabla的知识。 460 | - tips:在**介绍项目经历**的阶段中,“**在里面做什么事情**”是最重要的模块,一定要讲清楚,做什么事情决定你在面试官第一印象中的高度。而“**有什么难点,怎么克服的**”是给你一个埋坑的机会,让面试官更有可能在这个点上去追问你。 461 | - 收尾 462 | - 希望在未来,可以把这些知识(之前项目中提到的知识)运用在工作中。 463 | - 也希望在本次面试中,可以得到面试官的一些建议和指导。 464 | 465 | 466 | 467 | ​ 如果是三面/四面/HR面,并且你切实觉得可能不再那么纯技术了,你的自我介绍可以向软实力角度偏移,这里拿我的自我介绍给个例子: 468 | 469 | ​ tips:整理你自己的自我介绍的时候,请切记**不要太书面语**,背起来不舒服不说,真正到自我介绍的时候也会显得生硬。 470 | 471 | > ​ 您好,我是来自XX大学XX专业的XX。 472 | > 473 | > ​ 我想从技术理论、项目实践、工作能力上来简单介绍一下自己。 474 | > 475 | > ​ 首先是技术理论。我主攻的方向是Java后台。技术上来说,我对于Java基础、JVM、并发容器、线程池等等还是比较熟悉的,另外在框架方面用SpringBoot比较多,读过Spring的源码。而数据库层面的话,磁盘数据库中我对于MySQL尤其是InnoDB引擎比较熟悉,内存数据库中对于Redis比较熟悉。 476 | > 477 | > ​ 而在项目实践方面,我对于理论和实践的结合一直是比较看重的。从大一入学两个月我就有带领大一的学生队伍参加院里的项目比赛,之后陆陆续续到今天累计也做了不下十几个项目了。而在这些项目中,在综合方面我主要担任的也都是一个负责人的角色,而在技术方面我主要做的则都是Java后台的部分。我开过会议,出过文档,写过代码,也参加过有好几百人作为听众的大型答辩,应该说不管在硬实力还是软实力上,都得到了很不错的锻炼。 478 | > 479 | > ​ 最后是我的工作能力。我现今任我们专业XX班的班长,已经历时一年半,对于组织工作应该说是非常熟悉了。另外,我也有参与学校里一个较大型的信息技术工作室,出任其中的技术部负责人,偶尔会带领学弟学妹们进行一些项目实践,以及参与引导部门及工作室的规划安排。事务很多很杂,也培养了我强大的规划力和执行力,相信这也能够使我在未来的工作中得心应手。 480 | 481 |
482 | 483 |
484 | 485 |
486 | 487 | ## 面试中 488 | 489 | ### 技术面试的时候应该注意些什么 490 | 491 | 要从**表达**和**内容**两方面来讲。 492 | 493 | #### 表达 494 | 495 | 表达切记一定要**口齿清楚**,**逻辑清晰**。 496 | 497 | **口齿清楚** 498 | 口齿清楚这一点相信大家都能领会。但是也不排除有些小朋友一紧张口齿就不利索,这个一定要注意。 499 | 500 | 更有甚者有时候遇到不太确定的问题,想要以口齿不清来蒙混过关,这种更不可取,基本是搬石砸脚。 501 | 502 | **逻辑清晰** 503 | 504 | 如何保证这一点呢?有时候当遇到一个措手不及的问题,很难保证我们的逻辑又快又清晰。所以最简单的方式,就是用**速度的牺牲**来换取逻辑的明了。甚至有时候你可以短暂地沉默一会儿,好好整理一下思路,千万别张口就来,导致说得乱七八糟不成体系。 505 | 506 | 507 | 508 | #### 内容 509 | 510 | 对于一些切入点非常小的问题,直接回答就可以。比如说: 511 | 512 | > Q:对象的锁信息是存在哪里的? 513 | > 514 | > A:在对象头MarkWord里 515 | 516 | 517 | 518 | 但是对于一些切入点比较大的问题。例如: 519 | 520 | > Q:讲讲垃圾回收? 521 | > 522 | > Q:集合里ArrayList和LinkedList有什么区别? 523 | 524 | 回答的内容切记不可过于简短,但也不能胡乱堆砌。最好的方式是**水平扩展**或者**垂直扩展**。 525 | 526 | 不妨就拿上面的一个问题来举个例子。比如说讲讲垃圾回收。 527 | 528 | **水平扩展**的话,应该怎么讲呢? 529 | 530 | > A: 531 | > 532 | > 有关垃圾回收,我们首先要考虑两个问题。一是如何判断对象可回收,二是用什么样的方式来回收。 533 | > 534 | > 首先对于前者,有引用计数法和可达性分析两种方法,它们分别……(讲讲它们的含义,优缺点) 535 | > 536 | > 而对于后者,市面上主要有标记-清除,标记-整理,复制三种回收算法,它们分别……(讲讲含义,优缺点) 537 | > 538 | > 结合这些算法,市面上就出现了很多垃圾收集器,例如Serial,ParNew,CMS,G1……(顺便讲讲它们的回收逻辑,优缺点) 539 | > 540 | > 面试官:(暗自点头) 541 | 542 | 水平扩展也就是从一个问题出发,把与它相关的整棵知识树或者整颗知识树的一部分(如果树太大的话)讲出来,体现你的知识是有架构有体系的。 543 | 544 | 545 | 546 | **垂直扩展**的话,又应该怎么讲呢? 547 | 548 | > A: 549 | > 550 | > 垃圾回收的话,自JDK1.8后,市面上就非常流行G1垃圾回收器了。它是不分新生代和老年代的,基本原理是……(讲讲含义,优点) 551 | > 552 | > 但是垃圾的频繁回收势必会导致用户体验的下降,虽然G1已经很优秀了,作为开发者我们还是需要关注JVM的优化(话锋一转,开始走向深度) 553 | > 554 | > (讲一些具体的优化策略) 555 | > 556 | > 面试官:(暗自点头) 557 | 558 | 垂直扩展主要是需要自行找到一个切入点。或者面试官已经把切入点给到你了,那么接下来你要引入自己的思考,按照一个没有漏洞的逻辑走向深度,体现你是勤于思考并且善于发现并解决问题的人。 559 | 560 |
561 | 562 |
563 | 564 | ### 面试尬场怎么办 565 | 566 | 首先如果是因为你答不上来而尬场,你就乖乖说你不会,千万别强撑。 567 | 568 | 如果不是这个情况,是面试官一时沉默的话: 569 | 570 | 有时候面试官可能很忙,他可能都没来得及看完你的简历就来给你面试。换言之,他并没有做太多的准备。所以临时尬场也是非常正常的。 571 | 572 | 所以现在你要做的就是:让面试官不要尬场。 573 | 574 | 打个比方: 575 | 576 | > Q:你说你熟悉线程池,那你说说看线程池都有些什么重要的参数? 577 | > 578 | > A:比如有核心线程数、最大线程数、允许存活时间、阻塞队列等等(顺便简单说一下这几个参数都是什么含义) 579 | > 580 | > Q:嗯不错……(开始沉默) 581 | 582 | 一般面试官的沉默可能是你回答完这一个问题之后,他一时间想不到问什么。这个时候你可以继续沿着这个问题扩展下去: 583 | 584 | > A:关于线程池还有一些需要注意的地方。比如当一个任务添加进来的时候……(讲一下任务添加到线程池的流程) 585 | > 586 | > A:考虑到这样一个流程,所以我们在控制最大线程数、阻塞队列的时候是需要注意的。如果最大线程数设置过大,或者阻塞队列设置上限过长,可能导致OOM(又联系回前面参数的问题) 587 | 588 | 是不是联系回去不要紧,但是只要你依然在讲这个问题相关的知识树里的东西,就可以有效地缓解尬场,还会让面试官觉得: 589 | 590 | > 面试官:(嗯这小伙基础还挺扎实) 591 | 592 |
593 | 594 |
595 | 596 | ### 如何学会埋坑 597 | 598 | 埋坑是非常重要的一个技巧。 599 | 600 | 什么叫埋坑?就是在回答前一个问题的时候,适当地对面试官进行引导,让他能够问你想让他的问题。 601 | 602 | 面试前是最容易埋坑的。一是简历里的项目经历,专业技能;二是自我介绍阶段,讲一些自己擅长的技术点等等,引导面试官去问你。 603 | 604 | 而在面试的过程中,应该如何埋坑呢? 605 | 606 | 打个简单的比方。 607 | 608 | 比如现在面试官问你:”ArrayList和LinkedList有什么区别?“ 609 | 610 | 你先中规中矩地回答。回答完之后,我们可以根据我们的擅长点做一些不同的策略: 611 | 612 | 如果你比较擅长**并发容器**,你可以说: 613 | 614 | > ArrayList和LinkedList都属于单线程框架,是并发不安全的。如果要并发安全的话,需要使用CopyOnWriteArrayList或者ConcurrentLinkedQueue 615 | 616 | 617 | 618 | 如果你比较擅长**线程池**,你可以说: 619 | 620 | > ArrayList和LinkedList分别是对数组和链表的封装。JAVA中比较重要的对于数组、链表的封装集合类还有线程池中的阻塞队列,例如ArrayBlockingQeque和LinkedBlockingQueue 621 | 622 | 623 | 624 | 当然了,埋坑只能启到一定的导向作用,最终决定权还是在面试官手上。不要太刻意,不然会适得其反。 625 | 626 |
627 | 628 |
629 | 630 |
631 | 632 | ## 面试结束 633 | 634 | ### 面试官:”你有什么想问我的吗“该说什么? 635 | 636 | 这个问题可以给几个例子: 637 | 638 | 1. 您对我有什么建议吗? 639 | 2. 您平时工作中主要做些什么呢? 640 | 3. 如果我有幸能够被录取的话,主要会做一些什么样的工作呢? 641 | 642 |
643 | 644 |
645 | 646 | ### 我怎样才能知道我是否通过了 647 | 648 | 我个人不建议在面试完后直接问面试官:”我通过了吗?“ 649 | 650 | 而且面试本来就是双向选择,你是在众多offer中挑一个最喜欢的,人家也是在众多面试者中挑最好的那一批。 651 | 652 | 所以如果你想要知道自己是否通过了,最好的方式是从面试官对你的态度中窥探。 653 | 654 | 比如你最后问:”您平时工作中主要都做些什么呢?“ 655 | 656 | 他给你回答得非常详细,而且还会主动跟你聊一点别的,比如告诉你进来以后会做什么之类的,那说明他还是很认可你的。 657 | 658 | 如果他只是简单说一下,有点敷衍的样子,不排除只是他工作忙的可能,但你最好也可以考虑一下,找找其他出路了。 659 | 660 | 另外,等面试结束后,如果你有推荐你去面试的推荐人或者HR的联系方式,你也可以直接问他们面试的结果。 661 | 662 |
663 | 664 |
665 | 666 |
667 | 668 | ## 我两年半的技术人生 669 | 670 | 最后附上我两年半的技术生涯,中间不免有些弯路,但总体还是令我满意的。希望给有需要的人以参考。 671 | 672 | 我的时间线: 673 | 674 | - 2017(入学) 675 | - 7-8月:参加社会上的Java语言基础培训课程,学习Java,基本上到可以简单使用Swing的程度,对于反射泛型等中高级知识一窍不通; 676 | - 10月:拉了五个也是大一的小伙伴参加学院的创新创业比赛,基于Java Swing写了一个井字策略游戏,拿到学院的优胜奖; 677 | - 11月:机缘巧合认识了一位研二的研究生,经他推荐进入一位教授的实验室做商业网站; 678 | - 12月:在研究生的带领下认识SpringBoot和Mybatis,原理一窍不通,但是可以写一点CRUD; 679 | - 总结: 680 | - Java语言最基础内容的使用比较熟练了 681 | - 有了可怜到几乎可以忽略不计的web后台开发经验 682 | - 当时学院算法学习氛围比较好,到年末我大概相当于共计刷了一百道LeetCode中等题 683 | - 2018(大一-大二) 684 | - 1-3月寒假:进入培训机构学习web前端,了解了html-css-js,学会ajax; 685 | - 4-6月:划水度过,我不知道我在干嘛,就好好上课; 686 | - 7-8月暑假:做一些基于SpingBoot的简单实践,初步学习Unity游戏引擎; 687 | - 9-10月:划水; 688 | - 11-12月:学校各种创新比赛启动,我都参加了,最后同时启动了大约6-7个校园小项目; 689 | - 总结: 690 | - 这是我多角度尝试的一年(比如web前端、游戏开发),因为我还并没有决定以后从事什么方向。大约在暑假后(9月),我才觉得自己未来应该会做Java后台; 691 | - 在后台技术上提高不多,只是熟练了那么一丢丢; 692 | - 项目启动很多,那个时候觉得项目很重要很能锻炼人,后来觉得其实做那么多差不多级别的项目并不是很有意义。 693 | - 2019(大二-大三) 694 | - 1-3月寒假:我启动了太多项目,CRUD崽忙不过来了!! 695 | - 3-6月:各种项目比赛遍地开花的几个月 696 | - 3月:两个创新培育项目,一个国创一个市创评级 697 | - 4月:上海市计算机应用能力大赛二等奖 698 | - 6月:校大夏杯创业大赛铜奖 699 | - 但只有我自己心里清楚,奖是拿得很不错,但是技术上其实都是一样的东西,而且非常简单非常基础。全是传统的小型单体web项目,业务驱动。 700 | - 还是3-6月:比我大两级的拿到蚂蚁金服正式offer的学长来讲了Java面经,我终于意识到自己太菜了。但是跟着大三春招的洪流,我也没忍住投了几个简历试试: 701 | - 携程正式批次:一站式面试,我不知道一站式是什么意思,去面了一面自我感觉良好,就直接走了!走了!没在那等着继续面下去,就没了。 702 | - 字节跳动 · 北京:视频面试,在第二面挂了,评价是”相对大二学生来说比较优秀,但是暂时还没达到实习岗位的要求“ 703 | - 7月:买了一堆蚂蚁学长推荐的书籍,例如《高性能MySQL》、《深入理解Java虚拟机》、《Java并发编程实践》、《图解HTTP》等,开始有计划地阅读起来; 704 | - 8月:去安徽会场参加全国大学生计算机设计大赛的答辩,荣获一等奖。还是那句话,真没啥技术含量。但是提升了我答辩的能力~~和吹嘘的水平~~; 705 | - 9-12月:继续读书。 706 | - 总结: 707 | - 这是我从实践逐步转战理论学习的一年。之前的我太过于知其然而不知其所以然了,写项目都是不考虑原理的。自蚂蚁学长分享了Java面经之后,为我指出了方向。 708 | - 这一年我仔细阅读了《高性能MySQL》前七章,《深入理解Java虚拟机》近整本,也买了《MySQL技术内幕 InnoDB存储引擎》看完了一整本,做了很多笔记。 709 | - 我还买了很多其他杂七杂八的书,例如《轻量级微服务架构》、《图解TCP/IP》、《大型网站架构》等等,有些是几乎读不懂,有些是没怎么读,大抵算是这一年走的弯路。 710 | - 2020(大三) 711 | - 1月:疯狂整理、复习,中旬参加了携程的日常实习面试,通过了,年前进入携程实习; 712 | - 2月:过年几天还带了两本书回老家,但是真的学不进去。回来后因为疫情远程办公,同时不断进行复习与整理,准备春招; 713 | - 3月:开始进入春招面试阶段。只投了字节和阿里,都通过了; 714 | - 4月:一直到今天4.11,一直是静待offer。没有什么事情,就不疾不徐地学一学SpringCloudAlibaba,平时就~~谈谈恋爱~~,打打游戏,看看电影。 715 | 716 | 717 | 718 | ​ 我两年半的技术生涯,主要走的是 实践学习 → 理论学习 → 实践理论相结合 这样一条路线。 719 | 720 | ​ 一开始的实践学习就像是培训班生活,只不过是自我培训; 721 | 722 | ​ 然后到理论学习层面,有幸听到蚂蚁学长的面经讲座,加上有之前的实践基础,让我的理论学习有方向感且不会太虚浮; 723 | 724 | ​ 最后是实践理论相结合层面,主要就是把我一些新学习的没实践过的理论付诸实践。就比如学习了JVM调优和问题排查,就自己写一个死锁程序或者OOM程序然后用排查工具来排查一下问题。 725 | 726 | --------------------------------------------------------------------------------