标记-清除:标记已死对象,GC时直接清除。CMS回收器使用。
特点: 简单、速度快,但会留有内存空间碎片,空间碎片会导致后面的GC频率增加。
适合场景:只有小部分对象需要进行回收的,因为回收对象太多,其清除的时间就会越长。关注引用停顿时间,因为收集速度快,用户线程停顿的时间较短。
标记-复制:将内存分为两块区域,标记已死对象,GC时将存活对象复制到另一块区域,清除已死对象。
特点:收集速度快,可以避免空间碎片,但是有空间浪费,存活对象较多的情况下会非常耗时,而且需要担保机制。 适合场景: 只有少量对象存活的,因为需要把存活对象复制到新的内存区域,所需要复制 的对象越多那么其复制的时间就会越长,比较适合新生代的垃圾回收。
标记-整理:标记已死对象,GC时将存活对象复制到内存的另一端,将这一端的已死对象清除。
特点:相对于标记复制法不会浪费内存空间,相对标记清除法则可以避免空间碎片, 但是速度比其他两个算法慢。
适合场景: 内存吃紧,又要避免空间碎片的场景。
可作为GC root的对象:
虚拟机栈中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中引用的对象
隔离级别 | 可造成 | 简介 |
---|---|---|
读未提交(RU) | 脏读、不可重复读、幻读 | 事务A可以读取到事务B未commit前对记录做的修改 |
读提交(RC) | 不可重复读、幻读 | 事务A只可以读取到其它事务commit后的记录修改 |
可重复读(RR) | 幻读(可通过加next-key lock锁解决) | 事务A第二次读某条记录与第一次返回同样的值,即使这条记录被其它事务commit修改过 |
序列化(SERIALIZABLE) | 无 | 最高隔离级别,所有事务串行 |
redo log 和binlog的两阶段提交
事务执行中,mysql会写undo log 和redo log,同时事务此时是prepare状态
事务语句全部执行完,mysql写binlog日志,写完后事务可commit
redo log和change buffer的区别
redo log缓解了mysql随机写磁盘的压力
change buffer缓解了mysql随机读磁盘的压力
MySQL Replication(主从复制)
一主多从,主库提供写服务,从库提供读服务。
master通过binlog同步复制给从库实现数据一致性
无故障转移和负载均衡
MySQL Fabirc
在MySQL Replication基础上加入了故障检测、故障转移、数据分片功能,依旧是一主多从。
主节点挂掉后,会从从节点选一个提供写服务。
MySQL Cluster
只适用于NBD引擎。多主多从架构,可用性可达五个九,其分片数据也在不同的Data Nodes中备份。
以上三个并称为官方三兄弟
第三方插件提供的集群方案
以b开头以及以c开头的查询条件不走索引
偶尔慢
mysql在刷新"脏页"。脏页为记录在redo log中还没有来的及写磁盘的数据。本来mysql会在空闲时间去刷脏页,但是可能一直没有空闲时间,直到把redo log写满。mysql此时不得不暂停一些手里的事去刷新脏页,这时处理请求就会变慢
被某些事务锁住。可以使用show processlist查看所有连接的状态
经常慢
查看表上是否有索引
查看explain执行计划是否走了索引(mysql可能会选错索引)
查看是否有回表情况 是否可做优化
业务需要保证顺序消息时:通过自定义函数实现消息选择分区策略。保证顺序的消息写入到同一分区里,分里自动保证消息的顺序。
生产者:自身代码保证消息不发送重复,如果消息发送失败,则重试,直到成功为止。
消费者:处理完后提交offset,有可能消费完未提交offset时宕机,这时需要保证接口幂等性。
生产者:消息发送类型分为三种:
acks=0/1/all
0 代表生产者写入消息就不管了
1 代表至少有一个broker接受到消息就算发送成功
all 代表所有broker都同步完消息才返回成功
broker:需要根据实际情况通过参数控制几个副本写入成功才算成功
消费者:在业务处理结束后再提交offset
kafka最好是消费者与partition是一一对应的关系。如果消费者多,则多余的消费者空闲;如果partition多,则会有一个消费者消费多个partition的情况。
在消费者、partition有变动的情况,都会触发kafka的rebanlance。
前提:partition比消费者多的情况
AR:kafka集群中所有副本的统称
ISR:kafka集群中所有flowse的统称,是一个动态的集合
kafka通过两个变量来维持副本间的同步
HW:高水位,代表已经同步完的offset
LEO:下一条待写入的消息offset
拉
消费者与Broker采用长轮询方式拉取数据。
长轮询指消费者去broker拉消息,此时broker没有未消费消息或不够本次拉取数量,则消费者阻塞等待;等到broker有了消息之后唤醒消费者线程再拉取返回。避免没有消息时候多次频繁请求拉取。
1.8之后底层采用数组+链表/红黑树。链表转红黑树的机时是链表长度>8,红黑树转回链表的时机是红黑树的节点数量<6。这么做的原因是避免来回转换。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);这是HashMap的hash算法
前序:
public static void qianxu(TreeNode treeNode){
reuslt.add(treeNode);
if (treeNode.left != null) {
qianxu(treeNode.left);
}
if (treeNode.right != null) {
qianxu(treeNode.right);
}
}
中序:
public static void zhongxu(TreeNode treeNode) {
if (treeNode.left != null) {
zhongxu(treeNode.left);
}
reuslt.add(treeNode);
if (treeNode.right != null) {
zhongxu(treeNode.right);
}
}
后序:
public static void houxu(TreeNode treeNode) {
if (treeNode.left != null) {
zhongxu(treeNode.left);
}
if (treeNode.right != null) {
zhongxu(treeNode.right);
}
reuslt.add(treeNode);
}
明白区别了么?不明白就背。耗子尾汁
举个例子加深理解:某办事大厅,老百姓在排队办事,排了很多人。
非自旋锁:非自旋锁是个呆头鹅。一看前面排了这么多人啦,我先排队在这睡会,等轮到我了叫我啊。
自旋锁:自旋锁是个小机灵鬼。他不排队,先在旁边坐下迷糊一会,醒来了看看现在的情况是不是有个人刚办完事而后面的还没来的及补上,如果是则插队办事,不是则继续迷糊,如此循环。
适应性自旋锁:先问问小机灵鬼哥们他们迷糊了多久?迷糊了几次?根据情况来判断是睡还是迷糊。
无锁&偏向锁&轻量锁&重量锁
synchronized的四种锁状态
无锁:没有线程进入同步方法内。
偏向锁:当只有一个线程对同步方法访问时使用偏向锁。偏向锁的标志位记录了访问的threadID,在该线程获取或释放锁时不需要再通过CAS原子操作来加锁或释放锁,而是直接判断threadID是否相等。减少了CAS的原子操作开销。
轻量锁:轻量锁是通过自旋来等待持锁线程访问结束释放锁,自旋结束后尝试抢锁。
重量锁:重量锁是完全互斥的。某个锁正在被一个线程使用,其它线程阻塞等待锁释放后被唤醒。
锁升级过程: 只能升级,不能降级 无锁 ->偏向锁 当有线程第一次访问同步方法时,锁状态从无锁变成偏向锁,记录锁对象的偏向线程ID。 偏向锁 -> 轻量锁 当有其它线程访问同步方法时,锁升级为轻量锁。轻量锁通过线程自旋等待锁释放。 轻量锁 -> 重量锁 当被锁住的对象自旋时间过长或者等待线程变多,此时会升级为重量锁。等待线程会被阻塞等待唤醒。.
公平锁&非公平锁
场景:当有一个同步方法锁被占用,而队列中还有其它线程在等待。
公平锁
排到队尾,阻塞等待唤醒。 优点:线程有序执行,线程不会被饿死 缺点:整体吞吐量低,唤醒线程需要开销。
非公平锁
看看此时是不是持锁线程刚释放锁而队列中第一个线程又没来的及获取锁,如果是这种情况,则直接插队尝试获取锁。否则将排队阻塞。 优点:吞吐量高,减少了一部分唤醒线程的开销 缺点:有可能等待时间长的线程会饿死。
可重入锁&不可重入锁
可重入锁
线程在持有锁的时候,访问同对象锁的加锁方法会直接进入方法,不需要先释放锁再获取锁。synchronized和ReetrantLock都是可重入锁。 优点:一定程度避免死锁。
不可重入锁:
线程在持有锁时,访问同对象锁的其它加锁方法会先释放锁,再尝试获取锁。NonReentrantLock是不可重入锁的实现。
处理流程:
ReettrantLock和NonReentrantLock都继承于父类AQS,AQS中有个状态status来记录锁的重入次数。
可重入锁获取锁时判断status==0。如果为0则status =1 并进入方法执行。在同步方法中再次进入该锁对象其它同步方法时会判断该锁对象持有的线程是否是当前线程?如果是则status ++并进入方法。
释放时status-1,当status-1 = 0时,则代表可重入锁的锁已经释放完成。
关于锁,附上一篇特别好的博客,此处贴上链接
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
每当有任务进来,按这个顺序塞任务。核心线程 -> 阻塞队列 -> 非核心线程 ->拒绝,盗张图给你看
阻塞队列与其它队列的区别是阻塞队列为空时,线程过来取数据会阻塞在那里,当阻塞队列中有数据时,会自动唤醒阻塞的线程。当阻塞队列已满时,添加数据的线程会等待阻塞队列可用。
概念:分布式环境下某集群服务中某个接口保证线程同步机制,同一时刻只有一个线程执行此方法。
CAP:C一致性 A可用性 P分区容错性
两阶段提交
基于数据库层面引用协调者来协调分布式事务。协调询问各参与者事务是否执行成功,如果都成功,则通知所有参与者提交事务。如果有一个失败,则通知所有参与者回滚事务。
特点:基本保持数据一致性,牺牲了可用性。
TTC(补偿事务)
TTC是为事务的成功以及失败各准备一套方案。TTC为try\confirm\cancel。在某方法调用成功我们会做怎样的逻辑,失败后又会做怎样的逻辑。比较针对业务,开发成本高(需要实现不同的方法)
本地消息表(异步)
在本地数据库维护一张消息表,在这边方法执行成功后往消息表中插入数据,插入成功后推送到MQ中,MQ确认收到后消息表中删除这条数据。分布式的其方法消费这条消息,来做它对应的操作
事务消息
kafka和RocketMq实现事务消息。原理为系统A在RocketMQ中开启事务消息机制,发送一个"半消息"(含义为此消息未确认提交前消费者不可见),然后执行本地事务,事务执行成功后给MQ发送确认提交,之后消费者会收到该事务消息,再执行自己本地的事务,成功后提交消费确认。在系统A这边执行完任务因某原因不能确认提交的时候,RocketMq也提供一个事务反查功能,调用系统A实现的事务反查方法来查询事务结果。
事务传播是指spring在处理这种某事务方法调用其它事务方法时关于事务的选择机制。
在添加数据时,Redis如果检测到存储数据很多要超出内存限制,则会根据内存淘汰配置的机制去淘汰掉一些key。