深入Java核心、原理理解(面经)

本文深入探讨了Java的多线程、锁机制、JVM内存管理和GC,以及数据库、网络I/O模型和分布式架构。重点讲解了线程池原理、ThreadLocal实现、锁的升级(Synchronized优化)、JVM内存模型、数据库的ACID特性、MySQL引擎、索引原则和SQL优化。同时,还涵盖了数据结构、Netty中的重要组件、分布式架构中的CAP理论、Spring AOP和Spring Boot启动流程,以及Redis和Zookeeper的相关知识。内容详实,适合Java开发者深入学习和面试准备。
摘要由CSDN通过智能技术生成

回答循序渐进,多知识点分析,不要扩散回答。

1. 多线程

1)、线程池原理

线程创建的2中方式-Executors工厂方法
1. ExecutorService es1 = Executors.newSingleThreadExecutor() 单线程
2. ExecutorService es2 = Executors.newFixedThreadPool(3) 固定线程,内部使用LinkedBlockingQueue
3. ExecutorService es3 = Executors.newCachedThreadPool() 跟据需要创建新线程,内部使用SynchronousQueue
4.ScheduledExecutorService es5 = Executors.newSingleThreadScheduledExecutor() 只有一个线程的定时线程任务,内部使用DelayedWorkQueue
线程创建的2中方式-自定创建线程
线程参数
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize
,long keepAliveTime,TimeUnit unit
,BlockingQueue workQueue, RejectedExecutionHandler handler)
corePoolSize:线程池核心线程数量
maximumPoolSize:线程池最大线程数量
keepAliverTime:当活跃线程数大于核心线程数时,空闲线程最大存活时间
unit:存活时间的单位
workQueue:存放任务的队列,直接提交SynchronousQueue、无界队列LinkedBlockingQueue、有界队列ArrayBlockingQueue
handler:超出线程范围和队列容量的任务的处理程序(饱和策略)
提交任务的执行原理:
1、判断线程池里的核心线程是否都在执行任务,如果核心线程空闲或者还有核心线程没有被创建,则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
工作队列
1、ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
3、SynchronousQueue
一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool(5)使用了这个队列。
4、PriorityBlockingQueue
一个具有优先级的无限阻塞队列。
饱和策略
AbortPolicy中止策略、DiscardPolicy抛弃策略、DiscardOldestPolicy抛弃旧任务策略、CallerRunsPolicy调用者运行
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池大小如何定义:
具体定义需要跟据硬件磁盘和网络情况,还需要在服务器进行测试,下面是一种定义算法:
Ncpu=CPU核心数
Ucpu=cpu使用率,0~1
W/C=等待时间与计算时间的比率
公式:Nthreads=Ncpu*(1+w/c)
IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
计算密集型(cpu密集型)=Ncpu(常出现于线程中:复杂算法)

2)、Threadloacal 实现原理

实现原理
ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取,每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。

Threadloacal

应用场景
一般使用在数据库连接、Session管理场景

2. 锁机制

3)、锁的升级(锁膨胀)(jdk1.6之后的Synchronized)

相关概念
锁升级
对象的内存布局

当前只有一个线程时是偏向锁。
偏向锁:这个锁会偏向于第一个获得它的线程,在接下来的执行过程中,假如该锁没有被其他线程所获取,没有其他线程来竞争该锁,那么持有偏向锁的线程将永远不需要进行同步操作。
当出现俩个线程竞争锁,偏向锁失效,锁膨胀,就会升级为轻量级锁。
轻量级锁:自旋锁/自适应自旋锁,自旋锁超过一定的自旋次数会升级为重量级锁(默认情况下,自旋的次数为10次,可以通过-XX:PreBlockSpin来进行更改),自适应自旋锁是根据自旋成功获取锁的概率来决定是否升级为重量级锁。
重量级锁:重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。
重量级锁为什么开销大:主要是,当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cup。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。

4)、Volatile

《深入理解Java虚拟机》:“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
1、保证内存可见性: 在多核处理器中,当进行一个volatile变量的写操作时,编译器在写操作的指令前在上一个“lock”前缀。“lock”前缀的指令在多核处理器下会引发了以下两件事情是其内存可见性的根本原因:
1.将当前处理器缓存行的数据会写回到系统内存.
2.这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效.
2、阻止写指令重排: 根据 happens-before 规则, JMM 在每个 volatile 的写操作后面插入一个 StoreLoad 内存屏障, StoreLoad Barriers 会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令。

5)、Synchronized、JUC.lock\JUC.Atomic

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值