👏作者简介:大家好,我是 枫度柚子🍁,Java摆烂选手,很高兴认识大家 👀
📕B站: 枫吹过的柚 🍁
📕版本说明: 付费版本可以找UP主私聊,免费版本不再持续更新
🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
🛰微信群: 加微信 QaQ-linv
🐧QQ群: 995832569
🔑更新时间: 2024-03-08
ArrayList基于数组实现,LinkedList基于链表实现
ArryList适合随机查找,LinkedList适合增删,时间复杂度不同
ArrayList和LinkedList都是基于List接口实现的,LinkedList还实现了Deque接口,可以当做队列使用
详概
在JDK 7下
在JDK 8下
基于hash值计算得到数组对应的槽位
如果数组的槽位不存在元素就通过 CAS + 自旋 保证元素插入成功
如果在无需数组初始化、扩容而是存在hash冲突的情况下采用synchronized对Node节点加锁进行put,对应的槽位可能是链表或红黑树,如果链表长度达到8会转化为红黑树
简概
ConcurrentHashMap的扩容实质上也是数组的扩容,会重新开辟一份2倍原数组容量的数组将老数组的元素转移过来
在JDK 7下基于分段锁实现的,每个segment都维护了一个小HashMap,所以每个segment内部会进行扩容,操作和HashMap扩容一样
在JDK 8下倒序遍历数组的槽位,每经过一个槽位就通过synchronized对Node节点进行加锁,根据不同槽位类型(链表/红黑树)执行拷贝方法,对于红黑树会通过低位和高位(依旧是老位置的元素和新位置的元素)拆分成两个子链表存放到不同的槽位
参数说明
调优思路
参数参考
偏向锁
轻量级锁
自适应自旋
重量级锁
ReentrantLock是基于AQS实现的独占锁(可重入锁),可以用来保证线程安全以及线程同步操作,相对于synchronized的功能更灵活
支持公平锁、非公平锁,默认非公平锁
锁的可控性,可以自行把控加解锁的时机,但是异常时记得在finally显示释放锁
ReentrantLock的源码流程
public class 三个线程同时执行 { static CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) { new Thread(() -> { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程A执行,执行时间:" + System.currentTimeMillis()); }).start(); new Thread(() -> { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程B执行,执行时间:" + System.currentTimeMillis()); }).start(); new Thread(() -> { try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程C执行,执行时间:" + System.currentTimeMillis()); }).start(); countDownLatch.countDown(); } }
public class 三个线程同时执行 { static CyclicBarrier cyclicBarrier = new CyclicBarrier(3); public static void main(String[] args) { new Thread(() -> { try { cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println("线程A执行,执行时间:" + System.currentTimeMillis()); }).start(); new Thread(() -> { try { cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println("线程B执行,执行时间:" + System.currentTimeMillis()); }).start(); new Thread(() -> { try { cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } System.out.println("线程C执行,执行时间:" + System.currentTimeMillis()); }).start(); } }
public class 并发情况下三个线程依次执行 { private static volatile int count = 1; public static void main(String[] args) { new Thread(() -> { if (count == 1) { System.out.println("A"); } count = 2; }).start(); new Thread(() -> { if (count == 2) { System.out.println("B"); } count = 3; }).start(); new Thread(() -> { if (count == 3) { System.out.println("C"); } }).start(); } }
public class 并发情况下三个线程依次执行 { public static void main(String[] args) { CountDownLatch aLatch = new CountDownLatch(1); CountDownLatch bLatch = new CountDownLatch(1); CountDownLatch cLatch = new CountDownLatch(1); new Thread(() -> { try { aLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A"); bLatch.countDown(); }).start(); new Thread(() -> { try { bLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); cLatch.countDown(); }).start(); new Thread(() -> { try { cLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("C"); }).start(); aLatch.countDown(); } }
public class 并发情况下三个线程依次执行 { public static void main(String[] args) throws InterruptedException { Thread a = new Thread(() -> { System.out.println("A"); }); Thread b = new Thread(() -> { try { a.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); }); b.join(); Thread c = new Thread(() -> { try { b.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("C"); }); a.start(); b.start(); c.start(); } }
可以使用ReentrantLock和3个Condition来实现
public class 三个线程有序交错执行 { private static Lock lock = new ReentrantLock(); private static Condition conditionA = lock.newCondition(); private static Condition conditionB = lock.newCondition(); private static Condition conditionC = lock.newCondition(); public static void main(String[] args) { new Thread(() -> { System.out.print("A"); lock.lock(); try { conditionA.await(); conditionC.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } }).start(); new Thread(() -> { lock.lock(); try { conditionA.signal(); conditionB.await(); conditionC.signal(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } System.out.print("B"); }).start(); new Thread(() -> { lock.lock(); try { conditionB.signal(); } finally { lock.unlock(); } System.out.print("C"); }).start(); } }
互斥锁保证
public class 两个线程交替打印奇偶数 { private static int count = 0; private static final Object lock = new Object(); public static void main(String[] args) { new Thread(() -> { while (count < 100) { synchronized (lock) { if ((count & 1) == 0) { System.out.println("偶数: " + count++); } } } }).start(); new Thread(() -> { while (count < 100) { synchronized (lock) { if ((count & 1) == 1) { System.out.println("奇数: " + count++); } } } }).start(); } }
对于哪些是垃圾的算法(what)
对于垃圾怎么清理(how)
标记复制算法
标记清除算法
标记整理算法
分代理论
-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M
注意点
不要基于使用频率较低的列加索引
组合索引列、排序要遵循 左前缀原则
where 和 order by冲突时优先使用where,能用where过滤就不要使用having
不要用or连接索引列
索引列判空,比如is null、is not null不走索引
join要做到小表驱动大表
in、exists要做到小表驱动大表
索引列建议都设置为not null,可以节省1字节
对于获取总数count优先使用count(*) 或 count(1),如果数据量过大建议采用ES进行记录
频繁增删改的字段不要加索引,数据量一大维护成本会很高
长字符串可以采纳左20字符作为索引,满足90%场景
查询字段时,能采用覆盖索引的地方就去使用,避免查全部字段
流程
string
hash
list
set
zset(sorted set)
bitmap
stream
Reactor模型有三种
单线程模式
多线程模式(单线程、工作线程池)
主从多线程模式
Redis采用的是单线程模式
延时双删,先删除缓存,再写入数据库,延时500ms,再删除缓存
使用Redisson的读写锁,实现机制和ReentrantReadWriteLock一致
使用canal监听binlog及时去更新缓存
默认策略noeviction,当内存不足以容纳新写入数据时,新写入操作会报错
针对设置过期时间的key
volatile-lru,按照LRU算法删除
volatile-lfu,按照LFU算法删除
volatile-radom,随机删除
volatile-ttl,按过期时间顺序删除
针对所有key
Kafka的主要组件
Broker
Topic
Producer
Consumer
Consmer Group
Partition
Zookeeper
关键词
设计思路
参数
每个follower副本都会维护自己的offset以及计算好LEO反馈给leader副本,leader副本会通过follower副本反馈的LEO计算出HW值,然后再同步给所有follower,对于HW之前的说明是已经完成同步的,之后的说明可能是已经丢失的
leader副本出现故障
follower副本出现故障
对于每个Broker中记录的HW值,每次发生leader变更时,都会维护一个递增的epoch号以及当前分区写入的首个消息offset,并且持久化到文件中,其它follower会同步该文件,当出现leader变更时,follower就可以直接依赖最新的epoch去判断拉取消息的起点,避免使用自身的HW值去判断
生产端发往Broker端的确认策略有3种
acks = 0 就是不需要等任何副本确认收到,就可以继续发消息
acks = 1 就是只需要等leader副本确认写入完成,才可以继续发消息
acks = - 1 || all 就是需要等待所有副本都写入完成,才可以继续发消息
消息幂等性3种语义
at most once(最多收到一次)
at least lonce(至少收到一次)
exactly once(只收到一次)
对于跨网络的节点可能会丢消息,因为MQ存盘都会先写入OS的PageCache中,然后再让OS进行异步刷盘,如果缓存中的数据未及时写入硬盘就会导致消息丢失
对于Kafka如何保证消息不丢失,主要涉及 生产端到Broker端、Broker端持久化、Broker端到消费端
重试机制
异步处理
分批发送
错误处理
使用Kafka的生产端事务机制
Kafka的事务主要是保障一次发送多条消息的事务一致性,要么同时成功,要么同时失败,并不是分布式事务
Kafka事务的原子性是通过2PC两阶段提交来实现的
RocketMQ生产端有3种发送模式
必须等到Broker反馈之后才能继续发,安全性最高但发消息最慢
单向发送
异步发送
同步发送
单向发送
异步发送
对于跨网络的节点可能会丢消息,因为MQ存盘都会先写入OS的PageCache中,然后再让OS进行异步刷盘,如果缓存中的数据未及时写入硬盘就会导致消息丢失
对于RocketMQ如何保证消息不丢失,主要涉及 生产端到Broker端、Broker端持久化、Broker端到消费端
生产端到Broker端
RocketMQ生产端有3种发送模式
同步发送
单向发送
异步发送
生产端可以采用同步发送,根据Broker的反馈判断是否继续发送以及重试
可以使用事务消息
Broker端持久化
Broker端到消费端
另外RocketMQ服务需要有降级方案,对于RocketMQ来说,NameServer挂了,本身就无法保证消息不丢失了,所以应对这种场景,我们可以使用服务降级方案,将消息暂存到Redis、文件或内存中,等MQ服务恢复之后再将消息转移过去
MQ的顺序包含局部有序和全局有序
局部有序:(只保证一部分消息链路消费有序)
全局有序:(整个消息链路严格按照先进先出的顺序进行消费)
对于Kafka和RocketMQ消息积压并不会对性能有太大的影响,但是对于RabbitMQ就会导致性能直线下降
如何确定RocketMQ有大量的消息积压
如何处理大量积压的消息
BeanFactory 是Spring专门用来生成Bean、管理Bean的
ApplicationContext继承于BeanFactory,所以具有BeanFactory所有功能,并且还集成了国际化、系统环境变量等功能
简单工厂
由一个工厂类根据传入的参数,动态决定应该创建哪个实例
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是
在传入参数后创建还是传入参数前创建这个要根据具体情况来定
工厂方法
单例模式
适配器模式
装饰器模式
动态代理
观察者模式
策略模式
模板方法
可以,因为父容器的体现无非就是为了获取子容器不包含的Bean,如果全部包含在子容器完全用不到父容器了,所以是可以将Bean全部放在Spring MVC的子容器里来管理
虽然可以这么做但是不推荐这么做,如果项目里有用到事务或者AOP需要把这些配置也要配置到SpringMVC子容器的配置文件中,不然一部分配置在父容器一部分在子容器,这样可能导致功能不生效
通过spring-boot-plugin生成MANIFEST文件,其中main-class指定了运行java -jar的主程序,把依赖的jar文件打包在了Fat Jar
当我们执行指令java -jar,它就会去运行JarLauncher
所有依赖的jar文件都在/BOOT-INF/lib目录下以及所有依赖的class文件在BOO-INF/classes目录下
JarLauncher会根据路径去加载jar和class,加载之后,会去找到Start-Class然后使用反射去调用本地应用程序的Main方法
⾸先SpringBoot在启动时会先创建⼀个Spring容器
在创建Spring容器过程中,会利⽤@ConditionalOnClass技术来判断当前classpath中是否存在Tomcat依赖,如果存在则会⽣成⼀个启动Tomcat的Bean
Spring容器创建完之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端⼝等,然后启动Tomcat
Base理论 就是在分布式环境下,要保证 基本可用、软状态以及最终一致性
基本可用
软状态
最终一致性
Base理论就是对于CAP中C和A的权衡,我们无法保证强一致,但是能通过适当的方式保证最终一致性
本地事务 就是对于操作单一数据库的场景下的事务,ACIO特性是数据库直接支持的
分布式事务 就是在分布式环境下,需要保证不同服务的数据一致性,一般用于跨库事务、跨服务调用
分布式事务的实现方式
分布式事务存在的问题
对于分布式事务,在实际生产中可以异常落表定时自动巡检、基于RocketMQ事务消息、基于Seata