Redis 是如何实现分布式锁的
以 Redisson 为例, 实现方案为 lua 脚本 + Watch Dog , Redis 内部使用的数据结构为 Hash. Hash 内部有一对变量, 分别是客户进程 ID 和重入次数. Watch Dog 是在默认锁 30s 超时设置的时候添加的, 自定义超时时间没有看门狗.
Redisson 也是 Redis 官方推荐分布式锁实现方案,实现起来较为简单。
分布式锁的要求:
- 互斥性:在任意时刻,只能有一个进程持有锁。
- 防死锁:即使有一个进程在持有锁的期间崩溃而未能主动释放锁,要有其他方式去释放锁从而保证其他进程能获取到锁。
- 加锁和解锁的必须是同一个进程。
- 锁的续期问题。
IO 模型一共有五种, 是哪五种
- 阻塞
- 非阻塞
- 同步
- 一对一
- 多对多
- 异步
- 同步
通常情况下,用 select 进行 I/O 多路复用的程序设置的文件描述符数目限制是 1024。这个限制的来源是系统内核参数的限制,在 Linux 内核中叫做 FD_SETSIZE,其默认值为 1024。
而为什么要设置成 1024 呢?它是一个传统的数字,在很早以前就已经被定下来了。当时,1024 是一个相对较大的数字,因此大多数程序都不会因为文件描述符数目的限制而受到限制。
但是,随着硬件的提高,现在的系统可以打开的文件数目已经远大于 1024 了,因此,在使用 select 的程序中,需要注意文件描述符数目的限制。
线程池的参数:
核心线程数, 最大线程数, 任务队列, keepAlivedTime+时间单位, 线程工厂, 拒绝策略
任务队列:
- ArrayBlockingQueue: 出队入队同一个锁
- LinkedBlockQueue: 出对入队两个锁
- PriorityQueue: 实现 campareTo() 方法
- DelayQueue: 入队元素只有等到延迟期过了才能被获取
- SynchronousQueue: 没有容量, 即入即出
- LinkedTransferQueue: 先匹配再分配
- LinkedBlockingDuque: 双向链表
拒绝策略:
- CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
- AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
- DiscardPolicy - 直接丢弃,其他啥都没有
- DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
自旋锁(spinlock):
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。 获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。
重写 equals() 方法:
第一步: 重写 hashCode() 方法, 对一些不同的参数类型的 hashCode 的计算方法如下:
public int hashCode() {
int result = 17;
result = 31 * result + mInt; // Int
result = 31 * result + (mBoolean ? 1 : 0); // Boolean
result = 31 * result + Float.floatToIntBits(mFloat); // float
result = 31 * result + (int)(mLong ^ (mLong >>> 32)); // long
long mDoubleTemp = Double.doubleToLongBits(mDouble);
result =31 * result + (int)(mDoubleTemp ^ (mDoubleTemp >>> 32)); // Double
result = 31 * result + (mString == null ? 0 : mString.hashCode()); // String
result = 31 * result + (mObj == null ? 0 : mObj.hashCode()); // Object
return result;
}
第二步: 重写 equals(Object obj) 方法:
判断步骤:
-
是否同一个引用:
if(this == obj) return true; if(obj==null) return false;
-
是否是匹配的类: 通过 instanceof 和 getClass 来判断:
-
instanceof 判断可以是子类
String str = ""; str instanceof Object; // true
-
getClass() 要求全类名必须相同, 并且不看声明, 只看构造
Object o1 = new Object(); //class java.lang.Object Object o2 = new String("abc"); // class java.lang.String o1.getClass() == o2.getClass(); // false
-
-
是否成员都相等:
Object.equals(mem1,obj.mem1) && Object.equals(mem2,obj.mem2) ...
AOP
AOP(面向切面编程 Aspect,)是一种编程范式,用于在不修改原始代码的情况下向现有应用程序添加新功能。这种编程方式将应用程序分成许多独立的部分,称为切面。这些切面可以在应用程序的不同位置进行编写和维护,从而提高了应用程序的可重用性和可维护性。
最主要的是为了避免对原来的代码逻辑的破坏和侵入式操作, 使用 AOP 代理完成鉴权, 日志, 解密, 流量统计等操作.
两种方式, JDK 编译时织入, 只支持接口实现的, CGLIB 运行时织入, 通过继承的方式实现一个代理子类完成 AOP. 两个都只能在方法级织入
类加载的过程
--> 加载 = (双亲委派)
--> 验证 = (文件格式验证, 元数据验证, 字节码验证, 符号引用验证)
--> 准备 = (分配内存, 赋值默认值0, null)
--> 解析 = (符号引用到直接引用)
--> 初始化 = (对静态变量赋值, 完成类加载最后的过程)
Spring 创造 Bean
getBean() -> 实例化(相对应的工厂放入三级缓存) -> 属性注入 -> 初始化 -> 放入一级缓存(单例池) -> 结束
Spring循环依赖
Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects
),二级缓存为早期曝光对象earlySingletonObjects
,三级缓存为早期曝光对象工厂(singletonFactories
)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
MySQL 时间范围查询的效率
- 对于 InnoDB 引擎,没有索引的情况下(不建议),效率从高到低:int > UNIXTIMESTAMP(timestamp) > datetime(直接和时间比较) > timestamp(直接和时间比较)> UNIXTIMESTAMP(datetime)。
- 对于 InnoDB 引擎,建立索引的情况下,效率从高到低:int > datetime(直接和时间比较) > timestamp(直接和时间比较)> UNIXTIMESTAMP(timestamp) > UNIXTIMESTAMP(datetime)。
- 一句话,对于 MyISAM 引擎,采用 UNIX_TIMESTAMP(timestamp) 比较;对于InnoDB 引擎,建立索引,采用 int 或 datetime直接时间比较。