静态代理作用:通过代理对象访问目标对象(即被代理的对象),在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。代理类 即目标类(原始类) +加上额外功能 因为要屏蔽实现细节,所以它是和目标类实现的是相同的接口
但是,静态代理有一个缺点就是如果我想为每一个目标类的接口扩展相同的额外功能,那么就需要为每一个原始类,手动创建一个代理类,会造成代码冗余
动态代理,通过反射生成代理类
要扩展一个类有常见的两种方式,继承父类或实现接口,所以常见的有:JDK 动态代理和 CGLIB 动态代理
JDK
的动态代理是基于拦截器和反射来实现的。JDK
代理是不需要第三方库支持的,只需要 JDK
环境就可以进行代理,jdk动态代理实现:
InvocationHandler
接口,并重写其 invoke
方法,在 invoke
方法中可以利用反射机制调用目标类的方法,并可以在其前后添加一些额外的处理逻辑。参数 proxy:代表的是代理对象, method:原始方法(增加额外功能的那个原始方法), args:原始方法的参数Proxy.newProxyInstance
产生代理对象,newProxyInstance()
CGLIB通过字节码技术生成一个子类,并在子类中拦截父类方法的调用(这也就是为什么说 CGLIB 是基于继承的了),植入额外的业务逻辑。关CGLIB 引入一个新的角色方法拦截器,让其实现接口 MethodInterceptor
,并重写 intercept
方法,这里的 intercept
用于拦截并增强目标类的方法,最后,通过 Enhancer.create()
创建委托类对象的代理对象。
spring容器依据某种规则(自动装配的规则,有五种),自动建立对象之间的依赖关系。
1)no:
不支持自动装配功能。
2)default:
表示默认采用上一级标签的自动装配的取值。如果存在多个配置文件的话,那么每一个配置文件的自动装配方式都是独立的。
3)byName:
当一个bean节点带有 autowire=“byName” 的属性时:
①将查找其类中所有属性。
②去配置文件中查找是否存在id名称与属性名称相同的bean。
③如果有,就取得该bean对象,并调用属性所在类中的setter方法,将找到的bean注入;找不到注入null。
注意:不可能找到多个符合条件的bean(id唯一)
4)byType:@Autowired:spring注解,默认是以byType的方式去匹配类型相同的bean
当一个bean节点带有 autowire=“byType” 的属性时:
①将查找其类中所有属性。
②去配置文件中查找是否存在bean类型与属性类型相同的bean。
③如果有,就取得该bean对象,并调用属性所在类中的setter方法,将找到的bean注入;找不到注入null。
注意:找到多个符合条件的bean时会报错。
5)constructor:
使用构造方法完成对象注入,其实也是根据构造方法的参数类型进行对象查找,相当于采用byType的方式。即:根据构造方法的参数的数据类型,进行 byType 模式的自动装配。
bean
中,相互依赖对方,导致在创建的时候无法加载,比如现在有两个类AB相互依赖
Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。
bean是由Spring IoC容器实例化、组装和管理的对象。
对于普通的 Java 对象,当 new 的时候创建对象,然后该对象就能够使用了。一旦该对象不再被使用,则由 Java 自动进行垃圾回收。Spring 中的对象是 bean,bean 和普通的 Java 对象没啥大的区别,只不过 Spring 不再自己去 new 对象了,而是由 IoC 容器去帮助我们实例化对象并且管理它,Spring Bean 的生命周期完全由容器控制。
singleton bean: 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
生命周期
中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在
中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。Springboot启动时,是依靠启动类的main方法来进行启动的,而main方法中执行的是SpringApplication.run() 方法,而 SpringApplication.run() 方法中会创建spring的容器,并且刷新容器。而在刷新容器的时候就会去解析启动类,然后就会去解析启动类上的@SpringBootApplication 注解,而这个注解是个复合注解,这个注解中有一个@EnableAutoConfiguration 注解,这个注解就是开启自动配置,这个注解会去扫描我们所有jar包下的 META-INF 下的 spring.factories 文件,并将对应配置项用springFactorLoader通过反射加载到IOC容器中,这些配置里面都是有条件注解的,通常自动装配的类是@configuration注解修饰的类。
spring开启事务
IOC
和 AOP
容器的开源框架,其根本目的是为了简化Java开发。Java开发分为三层,controller层调用service层接口处理客户端请求,service层调用dao层实现业务逻辑,dao层与数据库交互,有strus2框架,mybatis框架都是只专注于一层的问题,而spring可以有的springmvc处理controller层问题,spring的AOP,IOC模块处理service层问题,与与各持久层框架整合解决DAO问题https://blog.csdn.net/weixin_38192427/article/details/116357609
springBoot
的定时任务需要两个注解 @EnableScheduling
和 @Scheduled
@EnableScheduling
:作用在启动类上,开启基于注解的定时任务@Scheduled
:作用在方法上,表示该方法为定时方法M 代表 模型(Model)
模型代表一个存取数据的对象
视图代表模型包含的数据的可视化
控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
![请添加图片描述](https://img-blog.csdnimg.cn/32c780937d754b18b4903fbee65912bd.png)
1、DispatcherServlet 接收到客户端发送的请求。
2、DispatcherServlet 收到请求调用HandlerMapping 处理器映射器。
3、HandleMapping 根据请求URL 找到对应的handler 以及 多个处理器拦截器,返回给DispatcherServlet
4、DispatcherServlet 根据handler 调用HanderAdapter 处理器适配器。适配器容易支持多种类型的处理器
5、HandlerAdapter 根据handler 执行处理器,也就是我们controller层写的业务逻辑,并返回一个ModeAndView
6、HandlerAdapter 返回ModeAndView 给DispatcherServlet
7、DispatcherServlet 调用 ViewResolver 视图解析器来 来解析ModeAndView
8、ViewResolve 解析ModeAndView 并返回真正的view 给DispatcherServlet
9、DispatcherServlet 将得到的视图进行渲染,填充到request域中
10、返回给客户端响应结果。
SpringMVC防止表单重复提交https://blog.csdn.net/weixin_38192427/article/details/121584020
submit
按钮,表单提交后用户点击浏览器的【刷新】或者后退等按钮会造成表单重复提交。url
和参数和这次的对比Mybatis如何将接口和xml连接
Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个、、、标签,都会被解析为一个MapperStatement对象。然后将其添加进 mappedStatements
缓存中,mappedStatements
数据结构是一个 StrictMap
,此处就是 Mapper
接口方法为什么不能重载的答案,• 将解析过的 mapper.xml
文件的 namespace
的绑定类型 type
放到 knownMappers
缓存中
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
如果有两个XML文件和这个Dao建立关系,岂不是冲突了?
不管有几个XML和Dao建立关系,只要保证namespace+id
唯一即可
• 将redo log buffer中的数据刷入到 redo log 文件中
• 将本次操作记录写入到 bin log文件中
• 将 bin log 文件名字和更新内容在 bin log 中的位置记录到redo log中,同时在 redo log 最后添加 commit 标记
version
字段来实现表锁:对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低
行级锁锁住的是表中的一行数据,行锁是开销最大的锁策略。行锁虽然开销大,锁表慢,但高并发下相比之下性能更高,发生锁冲突的概率最低,行级锁并不是锁记录,而是锁索引
行级锁导致的死锁
死锁导致原因:当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁
绝大部分情况使用行锁,但在个别特殊事务中,也可以考虑使用表锁
sql
语句的所查询字段和查询条件字段全都包含在一个索引或者(联合索引)中,那么就可以直接使用索引查询而不需要回表,这就是覆盖索引B+ Tree
,一颗 B+ Tree
只能根据一个索引值来构建,所以联合索引使用 最左
的字段来构建 B+ Tree
。之所以会有最左前缀匹配原则,是和联合索引的索引构建方式及存储结构 是有关系的,联合索引是使用多列索引的第一列(最左)构建的 B+ Tree
>
、<
、between
、like
)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4
如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。a = 1 and b = 2 and c = 3
建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。count(distinct col)/count(*)
,表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录。from_unixtime(create_time) = ’2014-05-29’
就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)。redis中 set()命令如何处理长度不一致的情况 (应该是SDS)
底层用SDS存String类型,空间预分配(len,减少分配次数,对字符串进行空间扩展的时候,扩展的内存比实际需要的多)和惰性空间(alloc,)释放(对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用 alloc
属性将这些字节的数量记录下来,等待后续使用也减少了分配次数比如缩减长度后又拼接)空间不够,扩容加倍现有的空间
字符串
list
set
key
。[SRANDMEMBER](https://redis.io/commands/srandmember)
,从集合中返回一个或多个随机元素但不删除它。count
参数时,回复将由最多count
成员组成,具体取决于集合的基数SINTER
求交集hash
Zset
ZRANGE zset (1 5 BYSCORE
“(”为不包括)轻松检索排名靠前的用户,还可以在给定用户名的情况下使用ZRANK(获取分数从高到低排序的元素的排名)返回其在列表中的排名。将 ZRANK 和 ZRANGE 一起使用,您可以向用户显示与给定用户相似的分数。一切都很快。HyperLogLogs(基数统计)
Bitmap
geospatial (地理位置)
基本数据类型底层实现
- 简单动态字符串 - sds
- Redis的默认字符串表示
- 二进制数据的一种结构, 具有动态扩容的特点.
- 内部结构`len` 保存了SDS保存字符串的长度 `buf[]` 数组用来保存字符串的每个元素+\0(复用c中函数) `alloc`分别以uint8, uint16, uint32, uint64表示整个SDS, 除过头部与末尾的\0, 剩余的字节数(`buf中未用字节长度`). `flags` 始终为一字节, 以低三位标示着头部的类型, 高5位未使用
- 为什么使用sds,为什么不用c的字符串
- 获取字符串长度的时间复杂度从n变为1 len属性
- 字符串拼接,之前可能会造成缓冲区溢出,现在会根据len判断,然后扩容
- 之前修改需要重新分配内存,空间预分配(len,减少分配次数)和惰性空间(alloc)释放(不立即回收,也减少了分配次数比如缩减长度后又拼接)
- 二进制安全,c是用空字符判断结尾(有些文件内容包含空字符),sds是len
- 压缩列表 - ZipList——hash
- 提高存储效率的双端列表,可以存整数和字符串,整数存的二进制而不是字符串,每次增删需要重新分配内存ziplist的内存
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILmbGHq9-1657541132500)(%E6%A2%B3%E7%90%86%202cbc4705c785478fb728537c33385252/Untitled%205.png)]
- entry 结构
- 一般结构 `:前一个entry的大小 当前entry的类型和长度 `存储entry表示的数据
- ` ` 整数,data存在encoding里面
- prevlen :小于254(255用于zlend)的时候,prevlen长度为1个字节,值即为前一个entry的长度,如果长度大于等于254的时候,prevlen用5个字节表示,第一字节设置为254,后面4个字节存储一个小端的无符号整型,表示前一个entry的长度;
- 为什么压缩空间
- 普通的数组,那么它每个元素占用的内存是一样的且取决于最大的那个元素**所以增加encoding字段**,针对不同的encoding来细化存储大小;**增加了prelen字段用于遍历**
- 缺点
- 不预留内存空间,每次增删都要重新分配空间,如果第一个结点改变,之后prevlen都要改变
- 快表 - QuickList——List
- ziplist为结点的双端链表结构
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5q2IF7nG-1657541132500)(%E6%A2%B3%E7%90%86%202cbc4705c785478fb728537c33385252/Untitled%206.png)]
- 指针字段会耗费大量内存
- 字典/哈希表 - hashTable—→ hash或者set
- 由数组 table 组成,元素是dictEntry,还有size(数组大小), masksize (求索引,-1),used(结点数量
- entry 有键有数值还有指向下一个的指针,数值可以是指针,可以是无符号整数,整数
- 链地址法
- 哈希表保存的键值对太多或者太少时,就要通过 rerehash(重新散列)来对哈希表进行相应的扩展或者收缩
- 根据原哈希表已使用的空间扩大/缩小一倍创建另一个哈希表
- 重新利用上面的哈希算法,计算索引值,然后将键值对放到新的哈希表位置上。
- 所有键值对都迁徙完毕后,释放原哈希表的内存空间。
- 扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的。
- 触发扩容的条件:
- 服务器目前没有执行 BGSAVE(BGSAVE 异步保存到磁盘) 命令或者 BGREWRITEAOF 命令(指示 Redis 启动[Append Only File](https://redis.io/topics/persistence#append-only-file)重写过程),并且负载因子大于等于1。
- 服务器没有执行,且负载因子大于等于5
ps:负载因子 = 哈希表已保存节点数量 / 哈希表大小。
- 整数集 - IntSet —> set
- 当一个集合只包含整数值元素,并且这个集合的元素数量不多时
- 当在一个int16类型的整数集合中插入一个int32类型的值,整个集合的所有元素都会转换成32类型。删除不会降级
- `encoding` 表示编码方式,的取值有三个:INTSET_ENC_INT16, INTSET_ENC_INT32, INTSET_ENC_INT64
- `length` 代表其中存储的整数的个数
- `contents` 指向实际存储数值的连续内存区域, 就是一个数组;整数集合的每个元素都是 contents 数组的一个数组项(item),各个项在数组中按值得大小**从小到大有序排序**,且数组中不包含任何重复项。(虽然 intset 结构将 contents 属性声明为 int8_t 类型的数组,但实际上 contents 数组并不保存任何 int8_t 类型的值,contents 数组的真正类型取决于 encoding 属性的值)
- 跳表 - ZSkipList —>有序列表 (最多多少层,怎么分配层 跳表及查询的时间复杂度
- 增删查改,在对数期望时间内完成,比平衡树更优雅,做范围查找的时候,平衡树比skiplist操作要复杂,实现简单且达到了类似效果(skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
- 缺点就是需要的存储空间比较大
- 多级索引,每层都是一个链表,上层结点指向下层值相同的结点,
- 最多logn层,第一层是n个结点,第二层是n/2个结点,第k层是n*(1/2的k-1次方)查询复杂度logn
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yoy2CJ7v-1657541132501)(%E6%A2%B3%E7%90%86%202cbc4705c785478fb728537c33385252/Untitled%207.png)]
先更新数据库,后删除缓存
key
过期策略
1.1.1. 定期删除 + 惰性删除是如何工作的
redis 默认会每隔 100ms 检查是否有过期的 key,如有过期的 key 则删除。需要说明的是,redis 不是每隔 100ms 将所有的 key 检查一次,而是随机抽取进行检查(如果每隔 100ms 对全部的 key 进行检查,那 redis 岂不是卡死)
因此,如果只采用定期删除策略,会导致很多 key 过期了,而没有删除。于是,惰性删除派上用场。也就是说在你获取某个 key 的时候 redis 会检查一下,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除(并不是 key 到了过期时间,就会被删除掉。而是在你获取查询这个 key 时,redis 再惰性的检查一下)
1.1.2. 定期删除 + 惰性删除就没问题了么
不是的,如果定期删除没有删除 key。然后你也没即时去请求 key,也就是说惰性删除也没生效。这样,redis 的内存会越来越高。那么就应该采用内存淘汰机制
缓存穿透:磁盘和redis都没有查询的数据,导致每次查找这个数据都要去磁盘找
检查不合法查询(id>0)
数据库和redis都没有,设置值为key-null (设置较短过期时间,防止正常状态 的使用)
布隆过滤器
布隆过滤器的特点是判断不存在的,则一定不存在;判断存在的,大概率存在,但也有小概率不存在。并且这个概率是可控的,我们可以让这个概率变小或者变高,取决于用户本身的需求
布隆过滤器由一个 bitSet 和 一组 Hash 函数(算法)组成,是一种空间效率极高的概率型算法和数据结构,主要用来判断一个元素是否在集合中存在,查询元素:通过所有的hash函数计算元素的下标,每个下表存储的值都为1,则有可能存在,只要有一个不为1就不存在。
缓存击穿 :缓存没有,数据库有(一般是缓存key过期) 并发多时,同时查询数据库(一条数据多个查询),会给数据库很大压力
缓存雪崩 : 缓存数据大批量过期,导致数据库压力过大
缓存污染:访问一两次后不再访问但一直存在缓存中,浪费空间 空间满了后要根据淘汰策略删除一些数据
redis (1)
JVM
是运行在操作系统之上的,与硬件实现无关,是Java跨平台的基础
Class
对象,JVM
自身需要的类,即包名为 java
、javax
、sun
等开头的类ext
目录下或者由系统变量 -Djava.ext.dir
指定位路径中的类库java -classpath
或 -D java.class.path
指定路径下的类库Bootstrap
类加载器外,其余的类加载器都应当有自己的父类加载器。如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。Java
类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载Java
核心 API
中定义类型不会被随意替换,比如用户自定义了一个oject类,如果不用双亲委派机制,则会出现不同类加载器产生的oject对象,会使java最基本的行为也无法保证,产生错误JVM
直接对栈的操作只有压栈和出栈,遵循先进后出原则,如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前栈帧。https://cloud.tencent.com/developer/article/1624694
定义:在 运行状态
中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java
语言的反射机制
反射的优点就是比较灵活,能够在运行时动态获取类的实例。
不过反射也存在很明显的缺点:
1)性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 Java 代码要慢很多。
2)安全问题:反射机制破坏了封装性,因为通过反射可以获取并调用类的私有方法和字段。
abstract
基本类型:快排
当待排序的数组中的元素个数较少时,源码中的阀值为7,采用的是插入排序。尽管插入排序的时间复杂度为0(n^2),但是当数组元素较少时,插入排序优于快速排序,因为这时快速排序的递归操作影响性能。
2)较好的选择了划分元(基准元素)。能够将数组分成大致两个相等的部分,避免出现最坏的情况。**例如当数组有序的的情况下,选择第一个元素作为划分元,将使得算法的时间复杂度达到O(n^2).
源码中选择划分元的方法:
当数组大小为 size=7 时 ,取数组中间元素作为划分元。int n=m>>1;(此方法值得借鉴)
当数组大小 7 当数组大小 size>40 时 ,从待排数组中较均匀的选择9个元素,选出一个伪中数做为划分元。
对象:归并,快速排序是不稳定的,对象数组中保存的只是对象的引用,这样多次移位并不会造成额外的开销,但是,对象数组对比较次数一般比较敏感,有可能对象的比较比单纯数的比较开销大很多。归并排序在这方面比快速排序做得更好,这也是选择它作为对象排序的一个重要原因之一。
equals() 与 == 的区别
null
ArrayList
添加元素的时候,扩容默认初始容量最小为 10CopyOnWriteArrayList
底层使用了独占式的可重入锁 ReentrantLock + volatile
volatile
修饰,来保证可见性ConcurrentHashMap
是线程安全的ConcurrentHashMap
不允许为 null
的 key
或者 value
HashMap
相同都是 16
hash
冲突,就直接 CAS
插入hash
冲突时,先使用 synchronized
加锁,在锁的内部处理 hash
冲突与 HashMap
是相同的,即使用 key
的 equals()
方法进行比较hash
值跟数组长度取与来决定放在数组的哪个位置如果出现放在同一个位置的时候,优先以链表的形式存放,在同一个位置的个数又达到了 8
个以上,如果数组的长度还小于 64
的时候,则会扩容数组。如果数组的长度大于等于 64
了的话,在会将该节点的链表转换成树Hashtable
处理线程安全问题过于简单粗暴,是将所有的方法都加上了 synchronized
关键字,在竞争激烈的并发hashCode()
不相等,则 equals()
一定不相等,不重写hashcode ()会造成当equals()判断相等时hashcode不相等equals()
相等,那么它们的 hashCode()
值一定相同,不重写equals()会造成equals()判断不相等时hashcode相等IO多路复用模型,,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核kernel能够通知程序进行相应的IO系统调用。
IO多路复用模型的基本原理就是select/epoll系统调用,单个线程不断的轮询select/epoll系统调用所负责的成百上千的socket连接,当某个或者某些socket网络连接有数据到达了,就返回这些可以读写的连接。因此,用select/epoll的优势在于,它可以同时处理成千上万个连接(connection)。与一条线程维护一个连接相比,I/O多路复用技术的最大优势是:系统不必创建线程,也不必维护这些线程,从而大大减小了系统的开销。
为什么需要四次挥手
为什么是三次?
为什么要等2MSL关闭
如何作流量控制?可靠传输
用滑动窗口,发送窗口由接受方窗口决定,接受方窗口由接受缓存决定
接收窗口
大小调整自己的发送窗口
大小;拥塞控制和流量控制的区别
拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;常用的方法就是:( 1 )慢开始、拥塞避免( 2 )快重传、快恢复。
流量控制:流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。
TCP是双工通信吗?什么是双工通信
TCP链接IP可以被篡改吗
TCP/UDP区别?UPD数据包一次可以发送多少大小?
如果已经建立了连接,但是客户端突然出现故障了怎么办
TCP 还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 秒钟发送一次。若一连发送 10 个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接
https://www.cnblogs.com/rever/p/14768553.html
join()
方法将挂起 调用线程
的执行任务,直到 被调用的对象
完成它的执行任务volatile
变量需要进⾏原⼦操作。 signal++
并不是⼀个原⼦操作,所以我们需要使⽤ synchronized
给它上锁
Java 内存模型定义了八个流程步骤来实现,数据私有区称为工作内存,数据共享区称为主内存
lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态
read:读取。作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中
load:载入。作用于工作内存的变量,从主内存中读取的变量值放入工作内存的变量副本中
use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作
assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中
write:写入。作用于主内存的变量,它把从工作内存中一个变量的值写入到主内存中
unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
Java线程池实现原理及其在美团业务中的实践
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
线程池7大核心参数
ThreadPoolExecutor 类中提交任务的方法
void execute(Runnable command)
Future submit(Callable task)
Future> submit(Runnable task) :虽然返回 Future,但是其 get() 方法总是返回 null
Future submit(Runnable task, T result)
Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。
线程池大小设置多少,根据什么考虑的?
cpu密集型,cpu个数+1
IO密集型:cpu个数乘以cpu利用率乘以总时间除以计算时间
线程池什么时候创建线程?
在创建了 ThreadPoolExecutor
线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来(执行了 execute()
方法)时才创建线程去执行任务
线程使用方式
有三种使用线程的方法:
一共有四种状态:无锁
、偏向锁
、轻量级锁
、重量级锁
,它会随着竞争情况逐渐升级。
是一个并发关键字,是一种抢占式非公平锁,• 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;每个实例都对应有自己的一把锁(this),不同实例之间互不影响,• synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
加锁和释放锁的原理底层用的指令码,保证可见性的原理:内存模型和happens-before规则,可重入原理:加锁次数计数器
ReentrantLock 来自于 jdk 1.5,位于 JUC 包的 locks 子包,独占(互斥)式可重入锁。Synchronized 的功能他都有,并且具有更加强大的功能
实现了 Lock 接口,具有通用的操作锁的方法。内部是使用 AQS 队列同步器来辅助实现的,重写了 AQS 的获取独占式锁的方法,并实现了可重入性
ReentrantLock 还具有公平与非公平两个获取锁模式,
可重入性
ReentrantLock 是一个可重入的互斥锁。顾名思义,互斥锁表示锁在某一时间点只能被同一线程所拥有。可重入表示锁可被某一线程多次获取。当然 synchronized 也是可重入的互斥锁
synchronized 关键字隐式的支持重进入,比如一个 synchronized 修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁
ReentrantLock 虽然没能像 synchronized 关键字一样支持隐式的重进入,但是在调用 lock() 方法时,已经获取到锁的线程,能够再次调用 lock() 方法获取锁而不被阻塞。主要的实现是在实现 tryAcquire(int acquires) 方法时考虑占有锁的线程再次获取锁的场景
在 ReentrantLock 中, AQS 的 state 同步状态值表示线程获取该锁的可重入次数
在默认情况下,state 的值为 0 表示当前锁没有被任何线程持有
当一个线程第一次获取该锁时会尝试使用 CAS 设置 state 的值为 1 ,如果 CAS 成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程
在该线程没有释放锁的情况下,第二次获取该锁后,状态值被设置为 2 , 这就是可重入次数
在该线程释放该锁时,会尝试使用 CAS 让状态值减 1,如果减 1 后状态值为 0,则当前线程成功释放该锁
ReentrantLock 是基于 AQS 实现的独占式可重入锁,同时还有公平模式和非公平模式
非公平模式
从源码中可以看出来,非公平锁的非公平之处在于:在头节点释放锁唤醒后继节点线程的时候,如果有新来的线程在尝试获取锁,那么新来的线程在和后继节点中的线程的竞争中可能获取锁成功,有两个地方都有支持这样的可能性
在 NonfairSync 类实现的 lock() 方法中,使用 CAS 方式获取到锁(只有在当前锁未被任何线程占有时才能成功,通过 state 是否等于 0 来判断)
在 NonfairSync 类实现的 tryAcquire(int acquires) 方法中,使用 CAS 方式获取到锁(只有在当前锁未被任何线程占有时才能成功,通过 state 是否等于 0 来判断)
如果被新来的线程获取到了锁,那么此时获取到锁的线程,不需要加入队列。而队列中被唤醒的后继节点,由于获取不到锁只能继续等待获得锁的节点线程释放了锁之后继续尝试
只要是进入同步队列排了队的节点线程,就只能按照队列顺序去获取锁,而没有排队的线程,则可能直接插队获取锁。如果在上面的两处获取锁都失败后,线程会被构造节点并加入同步队列等待,再也没有先一步获取锁的可能性
可以看出来,非公平模式下,同步队列中等待很久的线程相比还未进入队列等待的线程并没有优先权,甚至竞争也处于劣势:在队列中的线程要等待唤醒,并且还要检查前驱节点是否为头节点。在锁竞争激烈的情况下,在队列中等待的线程可能迟迟竞争不到锁,这也就是非公平模式,在高并发情况下会出现的饥饿问题
但是非公平模式会有良好的性能,直接获取锁线程不必加入等待队列就可以获得锁,免去了构造节点并加入同步队列的繁琐操作,因为加入到队列尾部时也是需要 CAS 竞争的,可能会造成 CPU 的浪费,并且还可能会因为后续的线程等待、唤醒,造成线程上下文切换,非常耗时
ReentrantLock源码解读_桐花思雨的博客-CSDN博客
t
在中断后停止,就必须先调用isInterrupted()
或者interrupted()
方法或者判断是否被中断,并为它增加相应的中断处理代码,****interrupt()
方法终止遇到 sleep()
或 wait()
,**Thread.sleep()
方法会抛出一个 InterruptedException
异常,当线程被 sleep()
休眠时,如果被中断,就会抛出这个异常,Thread.sleep()
方法由于中断而抛出的异常,是会清除中断标记的
volatile
修饰退出标志位,使线程正常退出,也就是当 run()
方法完成后线程中止(使用volatile目的是保证可见性,一处修改了标志,处处都要去主存读取新的值notEmpty、notFull
两个 Condition
条件对象 来分别实现消费者、生产者的等待唤醒,当获取数据的消费者线程被阻塞时会将该线程放置到 notEmpty
条件队列中,当插入数据的生产者线程被阻塞时,会将该线程放置到 notFull
条件队列中,对于线程的获取锁、释放锁、等待、唤醒等基本操作的源码都交给 ReentrantLock
和 Concition
来实现https://blog.csdn.net/weixin_38192427/article/details/117638584
如何设计一个高性能的无界阻塞队列?
putLock
作为消费线程获取的锁,同时有个对应的 notEmpty
条件对象用于消费线程的阻塞和唤醒,对移除数据采用 takeLock
作为生产线程获取的锁,同时有个对应的 notFull
条件对象用于生产线程的阻塞和唤醒,即入队锁和出队锁,这样避免了出队线程和入队线程竞争同一把锁的现象LinkedBlockingQueue
的工作模式都是 非公平 的,也不能手动指定为公平模式,这样的好处是可以提升并发量https://blog.csdn.net/weixin_38192427/article/details/117262033
volatile
时,线程在写入变量时,会把最新值刷新写回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值是一种乐观锁,乐观锁就是假定操作不会发生并发安全问题((不会有其他线程对数据进行修改)),因此不会加锁,但是在更新数据时会判断其他线程是否有没有对数据进行过修改。
比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步
函数包含3个操作数:
V:表示需要更新的值;
E:表示期望值;
N:表示要写入的新值;
思想是如果V相等于E,则将V的值写入N,若V不能于E,说明有其他线程做了更新,可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。
自旋CAS预测未来会获取到锁而不断循环等待。在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起
sleep()
CPU
调度机制sleep()
方法在同步上下文中调用,那么其他线程是无法进入到当前同步块或者同步方法中的interrupt()
方法来唤醒休眠线程wait()
notify()
:被调用后,锁不会自动释放,必须执行完 notify()
方法所在的 synchronized
代码块后才释放;只会随机任意的唤醒等待队列中的一个线程(不会通知优先级比较高的线程)
如何一次遍历,判断有环链表的环长度
slow和fast分别是指针,如果从链表头部开始往后移动,且fast指针移动速度为slow指针移动速度的两倍,fast 与low相遇则表示有环,环的长度等于第一次相遇时low走的长度