新开传奇网站发布网单,360路由器做网站,ceos 6.8安装wordpress,WordPress 整合源码1、HashMap底层源码 难度系数#xff1a;⭐⭐⭐
HashMap的底层结构在jdk1.7中由数组链表实现#xff0c;在jdk1.8中由数组链表红黑树实现#xff0c;以数组链表的结构为例。 JDK1.8之前Put方法#xff1a; JDK1.8之后Put方法#xff1a; HashMap基于哈希表的Map接口实…1、HashMap底层源码 难度系数⭐⭐⭐HashMap的底层结构在jdk1.7中由数组链表实现在jdk1.8中由数组链表红黑树实现以数组链表的结构为例。JDK1.8之前Put方法JDK1.8之后Put方法HashMap基于哈希表的Map接口实现是以key-value存储形式存在即主要用来存放键值对。HashMap 的实现不是同步的这意味着它不是线程安全的。它的key、value都可以为null。此外HashMap中的映射不是有序的。JDK1.8 之前 HashMap 由 数组链表 组成的数组是 HashMap 的主体链表则是主要为了解决哈希冲突(两个对象调用的hashCode方法计算的哈希码值一致导致计算的数组索引值相同)而存在的“拉链法”解决冲突.JDK1.8 以后在解决哈希冲突时有了较大的变化当链表长度大于阈值或者红黑树的边界值默认为 8并且当前数组的长度大于64时此时此索引位置上的所有数据改为使用红黑树存储。补充将链表转换成红黑树前会判断即使阈值大于8但是数组长度小于64此时并不会将链表变为红黑树。而是选择进行数组扩容。这样做的目的是因为数组比较小尽量避开红黑树结构这种情况下变为红黑树结构反而会降低效率因为红黑树需要进行左旋右旋变色这些操作来保持平衡 。同时数组长度小于64时搜索时间相对要快些。所以综上所述为了提高性能和减少搜索时间底层在阈值大于8并且数组长度大于64时链表才转换为红黑树。具体可以参考 treeifyBin方法。当然虽然增了红黑树作为底层数据结构结构变得复杂了但是阈值大于8并且数组长度大于64时链表转换为红黑树时效率也变的更高效。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho注意可以结合百度hashmap源码解析进行更深入的了解。2、JVM内存分哪几个区每个区的作用是什么 难度系数⭐⭐java虚拟机主要分为以下几个区方法区有时候也成为永久代在该区内很少发生垃圾回收但是并不代表不发生GC在这里进行的GC主要是对方法区里的常量池和对类型的卸载方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。该区域是被线程共享的。方法区里有一个运行时常量池用于存放静态编译产生的字面量和符号引用。该常量池具有动态性也就是说常量并不一定是编译时确定运行时生成的常量也会存在这个常量池中。虚拟机栈虚拟机栈也就是我们平常所称的栈内存,它为java方法服务每个方法在执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接和方法出口等信息。虚拟机栈是线程私有的它的生命周期与线程相同。局部变量表里存储的是基本数据类型、returnAddress类型指向一条字节码指令的地址和对象引用这个对象引用有可能是指向对象起始地址的一个指针也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定操作数栈的作用主要用来存储运算结果以及运算的操作数它不同于局部变量表通过索引来访问而是压栈和出栈的方式每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。本地方法栈本地方法栈和虚拟机栈类似只不过本地方法栈为Native方法服务。堆java堆是所有线程所共享的一块内存在虚拟机启动时创建几乎所有的对象实例都在这里创建因此该区域经常发生垃圾回收操作。程序计数器内存空间小字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。3、Java中垃圾收集的方法有哪些 难度系数⭐采用分区分代回收思想复制算法年轻代中使用的是Minor GC这种GC算法采用的是复制算法(Copying)a) 效率高缺点需要内存容量大比较耗内存b) 使用在占空间比较小、刷新次数多的新生区标记-清除老年代一般是由标记清除或者是标记清除与标记整理的混合实现a) 效率比较低会差生碎片。标记-整理老年代一般是由标记清除或者是标记清除与标记整理的混合实现a) 效率低速度慢需要移动对象但不会产生碎片。4、如何判断一个对象是否存活(或者GC对象的判定方法) 难度系数⭐引用计数法所谓引用计数法就是给每一个对象设置一个引用计数器每当有一个地方引用这个对象时就将计数器加一引用失效时计数器就减一。当一个对象的引用计数器为零时说明此对象没有被引用也就是“死对象”,将会被垃圾回收.引用计数法有一个缺陷就是无法解决循环引用问题也就是说当对象A引用对象B对象B又引用者对象A那么此时A,B对象的引用计数器都不为零也就造成无法完成垃圾回收所以主流的虚拟机都没有采用这种算法。可达性算法(引用链法)该算法的基本思路就是通过一些被称为引用链GC Roots的对象作为起点从这些节点开始向下搜索搜索走过的路径被称为Reference Chain)当一个对象到GC Roots没有任何引用链相连时即从GC Roots节点到该节点不可达则证明该对象是不可用的。在java中可以作为GC Roots的对象有以下几种虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象。5、什么情况下会产生StackOverflowError栈溢出和OutOfMemoryError堆溢出怎么排查 难度系数⭐⭐引发 StackOverFlowError 的常见原因有以下几种无限递归循环调用最常见执行了大量方法导致线程栈空间耗尽方法内声明了海量的局部变量native 代码有栈上分配的逻辑并且要求的内存还不小比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存64位 Linux。引发 OutOfMemoryError的常见原因有以下几种内存中加载的数据量过于庞大如一次从数据库取出过多数据集合类中有对对象的引用使用完后未清空使得JVM不能回收代码中存在死循环或循环产生过多重复的对象实体启动参数内存值设定的过小排查可以通过jvisualvm进行内存快照分析篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho栈溢出、堆溢出案例演示public class StackOverFlowTest {private static int count 1;public static void main(String[] args) {//模拟栈溢出//getDieCircle();//模拟堆溢出getOutOfMem();}public static void getDieCircle(){System.out.println(count);getDieCircle();}public static void getOutOfMem(){while (true) {Object o new Object();System.out.println(o);}}}Java6、什么是线程池线程池有哪些创建 难度系数⭐线程池就是事先将多个线程对象放到一个容器中当使用的时候就不用 new 线程而是直接去池中拿线程即可节省了开辟子线程的时间提高的代码执行效率在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。ExecutorService newCachedThreadPool Executors.newCachedThreadPool();ExecutorService newFixedThreadPool Executors.newFixedThreadPool(4);ScheduledExecutorService newScheduledThreadPool Executors.newScheduledThreadPool(4);ExecutorService newSingleThreadExecutor Executors.newSingleThreadExecutor();然后调用他们的 execute 方法即可。这4种线程池底层 全部是ThreadPoolExecutor对象的实现阿里规范手册中规定线程池采用ThreadPoolExecutor自定义的实际开发也是。newCachedThreadPool创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲线程若无可回收则新建线程。这种类型的线程池特点是工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。如果长时间没有往线程池中提交任务即如果工作线程空闲了指定的时间(默认为1分钟)则该工作线程将自动终止。终止后如果你又提交了新的任务则线程池重新创建一个工作线程。在使用CachedThreadPool时一定要注意控制任务的数量否则由于大量线程同时运行很有会造成系统瘫痪。newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程如果工作线程数量达到线程池初始的最大数则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时即线程池中没有可运行任务时它不会释放工作线程还会占用一定的系统资源。newSingleThreadExecutor创建一个单线程化的Executor即只创建唯一的工作者线程来执行任务它只会用唯一的工作线程来执行任务保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束会有另一个取代它保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务并且在任意给定的时间不会有多个线程是活动的。newScheduleThreadPool创建一个定长的线程池而且支持定时的以及周期性的任务执行。例如延迟3秒执行。7、为什么要使用线程池 难度系数⭐线程池做的工作主要是控制运行的线程数量处理过程中将任务放入队列然后在线程创建后启动这些任务如果线程数量超过了最 大数量超出数量的线程排队等候等其它线程执行完毕再从队列中取出任务来执行。主要特点:线程复用;控制最大并发数:管理线程。第一:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时任务可以不需要的等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进 行统一的分配调优和监控8、线程池底层工作原理 难度系数⭐第一步线程池刚创建的时候里面没有任何线程等到有任务过来的时候才会创建线程。当然也可以调用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法预创建corePoolSize个线程第二步调用execute()提交一个任务时如果当前的工作线程数corePoolSize直接创建新的线程执行这个任务第三步如果当时工作线程数量corePoolSize会将任务放入任务队列中缓存第四步如果队列已满并且线程池中工作线程的数量maximumPoolSize还是会创建线程执行这个任务第五步如果队列已满并且线程池中的线程已达到maximumPoolSize这个时候会执行拒绝策略JAVA线程池默认的策略是AbortPolicy即抛出RejectedExecutionException异常9、ThreadPoolExecutor对象有哪些参数 怎么设定核心线程数和最大线程数 拒绝策略有哪些 难度系数⭐参数与作用共7个参数corePoolSize核心线程数在ThreadPoolExecutor中有一个与它相关的配置allowCoreThreadTimeOut默认为false当allowCoreThreadTimeOut为false时核心线程会一直存活哪怕是一直空闲着。而当allowCoreThreadTimeOut为true时核心线程空闲时间超过keepAliveTime时会被回收。maximumPoolSize最大线程数线程池能容纳的最大线程数当线程池中的线程达到最大时此时添加任务将会采用拒绝策略默认的拒绝策略是抛出一个运行时错误RejectedExecutionException。值得一提的是当初始化时用的工作队列为LinkedBlockingDeque时这个值将无效。keepAliveTime存活时间当非核心空闲超过这个时间将被回收同时空闲核心线程是否回收受allowCoreThreadTimeOut影响。unitkeepAliveTime的单位。workQueue任务队列常用有三种队列即SynchronousQueue,LinkedBlockingDeque无界队列,ArrayBlockingQueue有界队列。threadFactory线程工厂ThreadFactory是一个接口用来创建worker。通过线程工厂可以对线程的一些属性进行定制。默认直接新建线程。RejectedExecutionHandler拒绝策略也是一个接口只有一个方法当线程池中的资源已经全部使用添加新线程被拒绝时会调用RejectedExecutionHandler的rejectedExecution法。默认是抛出一个运行时异常。线程池大小设置需要分析线程池执行的任务的特性 CPU 密集型还是 IO 密集型每个任务执行的平均时长大概是多少这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系如果是 CPU 密集型主要是执行计算任务响应时间很快cpu 一直在运行这种任务 cpu的利用率很高那么线程数的配置应该根据 CPU 核心数来决定CPU 核心数最大同时执行线程数加入 CPU 核心数为 4那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数1 如果是 IO 密集型主要是进行 IO 操作执行 IO 操作的时间较长这是 cpu 出于空闲状态导致 cpu 的利用率不高这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断等待时间越高那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。一个公式线程池设定最佳线程数目 线程池设定的线程等待时间线程 CPU 时间/线程 CPU 时间 * CPU 数目这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间通常使用 loadrunner测试大量运行次数求出平均值拒绝策略AbortPolicy直接抛出异常默认策略CallerRunsPolicy用调用者所在的线程来执行任务DiscardOldestPolicy丢弃阻塞队列中靠最前的任务并执行当前任务DiscardPolicy直接丢弃任务当然也可以根据应用场景实现 RejectedExecutionHandler 接口自定义饱和策略如记录日志或持久化存储不能处理的任务10、常见线程安全的并发容器有哪些 难度系数⭐CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMapCopyOnWriteArrayList、CopyOnWriteArraySet采用写时复制实现线程安全ConcurrentHashMap采用分段锁的方式实现线程安全11、Atomic原子类了解多少 原理是什么 难度系数⭐Java 的原子类都存放在并发包 java.util.concurrent.atomic下如下图基本类型使用原子的方式更新基本类型AtomicInteger整型原子类AtomicLong长整型原子类AtomicBoolean布尔型原子类数组类型使用原子的方式更新数组里的某个元素AtomicIntegerArray整形数组原子类AtomicLongArray长整形数组原子类AtomicReferenceArray引用类型数组原子类引用类型AtomicReference引用类型原子类AtomicStampedReference原子更新引用类型里的字段原子类AtomicMarkableReference 原子更新带有标记位的引用类型AtomicIntegerFieldUpdater原子更新整形字段的更新器AtomicLongFieldUpdater原子更新长整形字段的更新器AtomicStampedReference原子更新带有版本号的引用类型。该类将整数值与引用关联起来可用于解决原子的更新数据和数据的版本号以及解决使用 CAS 进行原子更新时可能出现的 ABA 问题AtomicInteger 类利用 CAS (Compare and Swap) volatile native 方法来保证原子操作从而避免 synchronized 的高开销执行效率大为提升。CAS 的原理是拿期望值和原本的值作比较如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是个本地方法这个方法是用来拿“原值”的内存地址返回值是 valueOffset另外value 是一个 volatile 变量因此 JVM 总是可以保证任意时刻的任何线程总能拿到该变量的最新值。12、synchronized底层实现是什么 lock底层是什么 有什么区别 难度系数⭐⭐⭐Synchronized原理方法级的同步是隐式即无需通过字节码指令来控制的它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置如果设置了执行线程将先持有monitor虚拟机规范中用的是管程一词然后再执行方法最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时当前线程试图获取monitor对象的所有权如果未加锁或者已经被当前线程所持有就把锁的计数器1当执行monitorexit指令时锁计数器-1当锁计数器为0时该锁就被释放了。如果获取monitor对象失败该线程则会进入阻塞状态直到其他线程释放锁。参考一篇文章讲透synchronized底层实现原理_忘了带罗盘的船夫的博客-CSDN博客Lock原理Lock的存储结构一个int类型状态值用于锁的状态变更一个双向链表用于存储等待中的线程Lock获取锁的过程本质上是通过CAS来获取状态值修改如果当场没获取到会将该线程放在线程等待链表中。Lock释放锁的过程修改状态值调整等待链表。Lock大量使用CAS自旋。因此根据CAS特性lock建议使用在低锁冲突的情况下。Lock与synchronized的区别Lock的加锁和解锁都是由java代码配合native方法调用操作系统的相关方法实现的而synchronize的加锁和解锁的过程是由JVM管理的当一个线程使用synchronize获取锁时若锁被其他线程占用着那么当前只能被阻塞直到成功获取锁。而Lock则提供超时锁和可中断等更加灵活的方式在未能获取锁的 条件下提供一种退出的机制。一个锁内部可以有多个Condition实例即有多路条件队列而synchronize只有一路条件队列同样Condition也提供灵活的阻塞方式在未获得通知之前可以通过中断线程以 及设置等待时限等方式退出条件队列。synchronize对线程的同步仅提供独占模式而Lock即可以提供独占模式也可以提供共享模式synchronizedLock关键字类自动加锁和释放锁需要手动调用unlock方法释放锁jvm层面的锁API层面的锁非公平锁可以选择公平或者非公平锁锁是一个对象,并且锁的信息保存在了对象中代码中通过int类型的state标识有一个锁升级的过程无13、了解ConcurrentHashMap吗 为什么性能比HashTable高说下原理 难度系数⭐⭐ConcurrentHashMap是线程安全的Map容器JDK8之前ConcurrentHashMap使用锁分段技术将数据分成一段段存储每个数据段配置一把锁即segment类这个类继承ReentrantLock来保证线程安全JKD8的版本取消Segment这个分段锁数据结构底层也是使用Node数组链表红黑树从而实现对每一段数据就行加锁也减少了并发冲突的概率。hashtable类基本上所有的方法都是采用synchronized进行线程安全控制高并发情况下效率就降低 ConcurrentHashMap是采用了分段锁的思想提高性能锁粒度更细化14、ConcurrentHashMap底层原理 难度系数⭐⭐⭐Java7 中 ConcurrentHashMap 使用的分段锁也就是每一个 Segment 上同时只有一个线程可以操作每一个 Segment 都是一个类似 HashMap 数组的结构它可以扩容它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。public V put(K key, V value) {SegmentK,V s;if (value null)throw new NullPointerException();int hash hash(key);// hash 值无符号右移 28位初始化时获得然后与 segmentMask15 做与运算// 其实也就是把高4位与segmentMask1111做与运算// this.segmentMask ssize - 1;//对hash值进行右移segmentShift位计算元素对应segment中数组下表的位置//把hash右移segmentShift相当于只要hash值的高32-segmentShift位右移的目的是保留了hash值的高位。然后和segmentMask与操作计算元素在segment数组中的下表int j (hash segmentShift) segmentMask;//使用unsafe对象获取数组中第j个位置的值后面加上的是偏移量if ((s (SegmentK,V)UNSAFE.getObject // nonvolatile; recheck(segments, (j SSHIFT) SBASE)) null) // in ensureSegment// 如果查找到的 Segment 为空初始化s ensureSegment(j);//插入segment对象return s.put(key, hash, value, false);}/*** Returns the segment for the given index, creating it and* recording in segment table (via CAS) if not already present.** param k the index* return the segment*/SuppressWarnings(unchecked)private SegmentK,V ensureSegment(int k) {final SegmentK,V[] ss this.segments;long u (k SSHIFT) SBASE; // raw offsetSegmentK,V seg;// 判断 u 位置的 Segment 是否为nullif ((seg (SegmentK,V)UNSAFE.getObjectVolatile(ss, u)) null) {SegmentK,V proto ss[0]; // use segment 0 as prototype// 获取0号 segment 里的 HashEntryK,V 初始化长度int cap proto.table.length;// 获取0号 segment 里的 hash 表里的扩容负载因子所有的 segment 的 loadFactor 是相同的float lf proto.loadFactor;// 计算扩容阀值int threshold (int)(cap * lf);// 创建一个 cap 容量的 HashEntry 数组HashEntryK,V[] tab (HashEntryK,V[])new HashEntry[cap];if ((seg (SegmentK,V)UNSAFE.getObjectVolatile(ss, u)) null) { // recheck// 再次检查 u 位置的 Segment 是否为null因为这时可能有其他线程进行了操作SegmentK,V s new SegmentK,V(lf, threshold, tab);// 自旋检查 u 位置的 Segment 是否为nullwhile ((seg (SegmentK,V)UNSAFE.getObjectVolatile(ss, u)) null) {// 使用CAS 赋值只会成功一次if (UNSAFE.compareAndSwapObject(ss, u, null, seg s))break;}}}return seg;}final V put(K key, int hash, V value, boolean onlyIfAbsent) {// 获取 ReentrantLock 独占锁获取不到scanAndLockForPut 获取。HashEntryK,V node tryLock() ? null : scanAndLockForPut(key, hash, value);V oldValue;try {HashEntryK,V[] tab table;// 计算要put的数据位置int index (tab.length - 1) hash;// CAS 获取 index 坐标的值HashEntryK,V first entryAt(tab, index);for (HashEntryK,V e first;;) {if (e ! null) {// 检查是否 key 已经存在如果存在则遍历链表寻找位置找到后替换 valueK k;if ((k e.key) key ||(e.hash hash key.equals(k))) {oldValue e.value;if (!onlyIfAbsent) {e.value value;modCount;}break;}e e.next;}else {// first 有值没说明 index 位置已经有值了有冲突链表头插法。if (node ! null)node.setNext(first);elsenode new HashEntryK,V(hash, key, value, first);int c count 1;// 容量大于扩容阀值小于最大容量进行扩容if (c threshold tab.length MAXIMUM_CAPACITY)rehash(node);else// index 位置赋值 nodenode 可能是一个元素也可能是一个链表的表头setEntryAt(tab, index, node);modCount;count c;oldValue null;break;}}} finally {unlock();}return oldValue;}运行项目并下载源码Java篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1hoJava8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构Node 数组 链表 / 红黑树Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树在冲突小于一定数量时又退回链表。public V put(K key, V value) {return putVal(key, value, false);}/** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {// key 和 value 不能为空if (key null || value null) throw new NullPointerException();int hash spread(key.hashCode());int binCount 0;for (NodeK,V[] tab table;;) {// f 目标位置元素NodeK,V f; int n, i, fh;// fh 后面存放目标位置的元素 hash 值if (tab null || (n tab.length) 0)// 数组桶为空初始化数组桶自旋CAS)tab initTable();else if ((f tabAt(tab, i (n - 1) hash)) null) {// 桶内为空CAS 放入不加锁成功了就直接 break 跳出if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null)))break; // no lock when adding to empty bin}else if ((fh f.hash) MOVED)tab helpTransfer(tab, f);else {V oldVal null;// 使用 synchronized 加锁加入节点synchronized (f) {if (tabAt(tab, i) f) {// 说明是链表if (fh 0) {binCount 1;// 循环加入新的或者覆盖节点for (NodeK,V e f;; binCount) {K ek;if (e.hash hash ((ek e.key) key ||(ek ! null key.equals(ek)))) {oldVal e.val;if (!onlyIfAbsent)e.val value;break;}NodeK,V pred e;if ((e e.next) null) {pred.next new NodeK,V(hash, key,value, null);break;}}}else if (f instanceof TreeBin) {// 红黑树NodeK,V p;binCount 2;if ((p ((TreeBinK,V)f).putTreeVal(hash, key,value)) ! null) {oldVal p.val;if (!onlyIfAbsent)p.val value;}}}}if (binCount ! 0) {if (binCount TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal ! null)return oldVal;break;}}}addCount(1L, binCount);return null;}运行项目并下载源码Java15、了解volatile关键字不 难度系数⭐volatile是Java提供的最轻量级的同步机制保证了共享变量的可见性被volatile关键字修饰的变量如果值发生了变化其他线程立刻可见避免出现脏读现象。volatile禁止了指令重排可以保证程序执行的有序性但是由于禁止了指令重排所以JVM相关的优化没了效率会偏弱16、synchronized和volatile有什么区别 难度系数⭐⭐volatile本质是告诉JVM当前变量在寄存器中的值是不确定的需要从主存中读取synchronized则是锁定当前变量只有当前线程可以访问该变量其他线程被阻塞住。volatile仅能用在变量级别而synchronized可以使用在变量、方法、类级别。volatile仅能实现变量的修改可见性不能保证原子性而synchronized则可以保证变量的修改可见性和原子性。volatile不会造成线程阻塞synchronized可能会造成线程阻塞。volatile标记的变量不会被编译器优化synchronized标记的变量可以被编译器优化。17、Java类加载过程 难度系数⭐加载 加载时类加载的第一个过程在这个阶段将完成一下三件事情通过一个类的全限定名获取该类的二进制流。将该二进制流中的静态存储结构转化为方法去运行时数据结构。在内存中生成该类的Class对象作为该类的数据访问入口。验证 验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:文件格式验证验证字节流是否符合Class文件的规范如主次版本号是否在当前虚拟机范围内常量池中的常量是否有不被支持的类型.元数据验证:对字节码描述的信息进行语义分析如这个类是否有父类是否集成了不被继承的类等。字节码验证是整个验证过程中最复杂的一个阶段通过验证数据流和控制流的分析确定程序语义是否正确主要针对方法体的验证。如方法中的类型转换是否正确跳转指令是否正确等。符号引用验证这个动作在后面的解析过程中发生主要是为了确保解析动作能正确执行。准备准备阶段是为类的静态变量分配内存并将其初始化为默认值这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存实例变量将会在对象实例化时随着对象一起分配在Java堆中。解析该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前也有可能在初始化之后。初始化初始化时类加载的最后一步前面的类加载过程除了在加载阶段用户应用程序可以通过自定义类加载器参与之外其余动作完全由虚拟机主导和控制。到了初始化阶段才真正开始执行类中定义的Java程序代码。18、什么是类加载器类加载器有哪些 难度系数⭐类加载器就是把类文件加载到虚拟机中也就是说通过一个类的全限定名来获取描述该类的二进制字节流。主要有以下四种类加载器启动类加载器(Bootstrap ClassLoader)用来加载java核心类库无法被java程序直接引用扩展类加载器(extension class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类系统类加载器system class loader也叫应用类加载器它根据 Java 应用的类路径CLASSPATH来加载 Java 类。一般来说Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它用户自定义类加载器通过继承 java.lang.ClassLoader类的方式实现什么时候会使用到加载器java中的加载器是按需加载什么时候用到什么时候加载new对象的时候访问某个类或者接口的静态变量或者对该静态变量赋值时调用类的静态方法时反射初始化一个类的子类时其父类首先会被加载JVM启动时标明的启动类也就是文件名和类名相同的那个类19、简述java内存分配与回收策略以及Minor GC和Major GCfull GC 难度系数⭐⭐内存分配栈区栈分为java虚拟机栈和本地方法栈堆区堆被所有线程共享区域在虚拟机启动时创建唯一目的存放对象实例。堆区是gc的主要区域通常情况下分为两个区块年轻代和年老代。更细一点年轻代又分为Eden区主要放新创建对象From survivor 和 To survivor 保存gc后幸存下的对象默认情况下各自占比 8:1:1。方法区被所有线程共享区域用于存放已被虚拟机加载的类信息常量静态变量等数据。被Java虚拟机描述为堆的一个逻辑部分。习惯是也叫它永久代permanment generation程序计数器当前线程所执行的行号指示器。通过改变计数器的值来确定下一条指令比如循环分支跳转异常处理线程恢复等都是依赖计数器来完成。线程私有的。回收策略以及Minor GC和Major GC对象优先在堆的Eden区分配大对象直接进入老年代长期存活的对象将直接进入老年代当Eden区没有足够的空间进行分配时虚拟机会执行一次Minor GC.Minor GC通常发生在新生代的Eden区在这个区的对象生存期短往往发生GC的频率较高回收速度比较快;Full Gc/Major GC 发生在老年代一般情况下触发老年代GC的时候不会触发Minor GC,但是通过配置可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。20、如何查看java死锁 难度系数⭐####演示死锁package com.ssg.mst;public class 死锁 {private static final String lock1 lock1;private static final String lock2 lock2;public static void main(String[] args) {Thread thread1 new Thread(() - {while (true) {synchronized (lock1) {try {System.out.println(Thread.currentThread().getName() lock1);Thread.sleep(1000);synchronized (lock2){System.out.println(Thread.currentThread().getName() lock2);}} catch (InterruptedException e) {throw new RuntimeException(e);}}}});Thread thread2 new Thread(() - {while (true) {synchronized (lock2) {try {System.out.println(Thread.currentThread().getName() lock2);Thread.sleep(1000);synchronized (lock1){System.out.println(Thread.currentThread().getName() lock1);}} catch (InterruptedException e) {throw new RuntimeException(e);}}}});thread1.start();thread2.start();}}运行项目并下载源码Java死锁代码演示程序运行进程没有停止。通过jps查看java进程找到没有停止的进程通过jstack 9060 查看进程具体执行信息21、Java死锁如何避免 难度系数⭐造成死锁的几个原因1.一个资源每次只能被一个线程使用2.一个线程在阻塞等待某个资源时不释放已占有资源3.一个线程已经获得的资源在未使用完之前不能被强行剥夺4.若干线程形成头尾相接的循环等待资源关系这是造成死锁必须要达到的4个条件如果要避免死锁只需要不满足其中某一个条件即可。而其中前3个条件是作为锁要符合的条件所以要避免死锁就需要打破第4个条件不出现循环等待锁的关系。在开发过程中1.要注意加锁顺序保证每个线程按同样的顺序进行加锁2.要注意加锁时限可以针对锁设置一个超时时间3.要注意死锁检查这是一种预防机制确保在第一时间发现死锁并进行解决第三章-java框架篇1、简单的谈一下SpringMVC的工作流程 难度系数⭐用户发送请求至前端控制器DispatcherServletDispatcherServlet收到请求调用HandlerMapping处理器映射器。处理器映射器找到具体的处理器生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。DispatcherServlet调用HandlerAdapter处理器适配器HandlerAdapter经过适配调用具体的处理器(Controller也叫后端控制器)。Controller执行完成返回ModelAndViewHandlerAdapter将controller执行结果ModelAndView返回给DispatcherServletDispatcherServlet将ModelAndView传给ViewReslover视图解析器ViewReslover解析后返回具体ViewDispatcherServlet根据View进行渲染视图即将模型数据填充至视图中。DispatcherServlet响应用户2、说出Spring或者SpringMVC中常用的5个注解 难度系数⭐Component 基本注解标识一个受Spring管理的组件Controller 标识为一个表示层的组件Service 标识为一个业务层的组件Repository 标识为一个持久层的组件Autowired 自动装配Qualifier() 具体指定要装配的组件的id值RequestMapping() 完成请求映射PathVariable 映射请求URL中占位符到请求处理方法的形参只要说出几个注解并解释含义即可如上答案只做参考3、简述SpringMVC中如何返回JSON数据 难度系数⭐Step1在项目中加入json转换的依赖例如jacksonfastjsongson等Step2在请求处理方法中将返回值改为具体返回的数据的类型 例如数据的集合类ListEmployee等Step3在请求处理方法上使用ResponseBody注解4、谈谈你对Spring的理解 难度系数⭐Spring 是一个开源框架为简化企业级应用开发而生。Spring 可以是使简单的JavaBean 实现以前只有EJB 才能实现的功能。Spring 是一个 IOC 和 AOP 容器框架。Spring 容器的主要核心是控制反转IOC传统的 java 开发模式中当需要一个对象时我们会自己使用 new 或者 getInstance 等直接或者间接调用构造方法创建一个对象。而在 spring 开发模式中spring 容器使用了工厂模式为我们创建了所需要的对象不需要我们自己创建了直接调用spring 提供的对象就可以了这是控制反转的思想。依赖注入DIspring 使用 javaBean 对象的 set 方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程就是依赖注入的思想。面向切面编程AOP在面向对象编程oop思想中我们将事物纵向抽成一个个的对象。而在面向切面编程中我们将一个个的对象某些类似的方面横向抽成一个切面对这个切面进行一些如权限控制、事物管理记录日志等公用操作处理的过程就是面向切面编程的思想。AOP 底层是动态代理如果是接口采用 JDK 动态代理如果是类采用CGLIB 方式实现动态代理。5、Spring中常用的设计模式 难度系数⭐代理模式——spring 中两种代理方式若目标对象实现了若干接口spring 使用jdk 的java.lang.reflect.Proxy类代理。若目标兑现没有实现任何接口spring 使用 CGLIB 库生成目标类的子类。单例模式——在 spring 的配置文件中设置 bean 默认为单例模式。模板方式模式——用来解决代码重复的问题。比如RestTemplate、JmsTemplate、JpaTemplate工厂模式——在工厂模式中我们在创建对象时不会对客户端暴露创建逻辑并且是通过使用同一个接口来指向新创建的对象。Spring 中使用 beanFactory 来创建对象的实例。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho6、Spring循环依赖问题 难度系数⭐⭐常见问法请解释一下spring中的三级缓存三级缓存分别是什么?三个Map有什么异同?什么是循环依赖?请你谈谈?看过spring源码吗?如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?多例的情况下,循环依赖问题为什么无法解决?什么是循环依赖?两种注入方式对循环依赖的影响?相关概念实例化:堆内存中申请空间初始化:对象属性赋值三级缓存名称对象名含义一级缓存singletonObjects存放已经经历了完整生命周期的Bean对象二级缓存earlySingletonObjects存放早期暴露出来的Bean对象Bean的生命周期未结束属性还未填充完)三级缓存singletonFactories存放可以生成Bean的工厂四个关键方法package org.springframework.beans.factory.support;public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {/**单例对象的缓存:bean名称—bean实例即:所谓的单例池。表示已经经历了完整生命周期的Bean对象第一级缓存*/private final MapString, Object singletonObjects new ConcurrentHashMap(256);/**早期的单例对象的高速缓存: bean名称—bean实例。表示 Bean的生命周期还没走完Bean的属性还未填充就把这个 Bean存入该缓存中也就是实例化但未初始化的 bean放入该缓存里第二级缓存*/private final MapString, Object earlySingletonObjects new HashMap(16);/**单例工厂的高速缓存:bean名称—ObjectFactory表示存放生成 bean的工厂第三级缓存*/private final MapString, ObjectFactory? singletonFactories new HashMap(16);}debug源代码过程需要22个断点(可选)1A创建过程中需要B于是A将自己放到三级缓里面去实例化B2B实例化的时候发现需要A于是B先查一级缓存没有再查二级缓存还是没有再查三级缓存找到了A然后把三级缓存里面的这个A放到二级缓存里面并删除三级缓存里面的A3B顺利初始化完毕将自己放到一级缓存里面(此时B里面的A依然是创建中状态)然后回来接着创建A此时B已经创建结束直接从一级缓存里面拿到B然后完成创建并将A自己放到一级缓存里面。总结1Spring创建 bean主要分为两个步骤创建原始bean对象接着去填充对象属性和初始化。2每次创建 bean之前我们都会从缓存中查下有没有该bean因为是单例只能有一个。3当创建 A的原始对象后并把它放到三级缓存中接下来就该填充对象属性了这时候发现依赖了B接着就又去创建B同样的流程创建完B填充属性时又发现它依赖了A又是同样的流程不同的是这时候可以在三级缓存中查到刚放进去的原始对象A。所以不需要继续创建用它注入 B完成 B的创建既然 B创建好了所以 A就可以完成填充属性的步骤了接着执行剩下的逻辑闭环完成Spring解决循环依赖依靠的是Bean的中间态这个概念而这个中间态指的是已经实例化但还没初始化的状态—半成品。实例化的过程又是通过构造器创建的如果A还没创建好出来怎么可能提前曝光所以构造器的循环依赖无法解决其他衍生问题问题1:为什么构造器注入属性无法解决循环依赖问题?由于spring中的bean的创建过程为先实例化 再初始化(在进行对象实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用,然而使用构造器注入,必须要使用构造器完成对象的初始化的操作,就会陷入死循环的状态问题2:一级缓存能不能解决循环依赖问题? 不能在三个级别的缓存中存储的对象是有区别的 一级缓存为完全实例化且初始化的对象 二级缓存实例化但未初始化对象 如果只有一级缓存,如果是并发操作下,就有可能取到实例化但未初始化的对象,就会出现问题问题3:二级缓存能不能解决循环依赖问题?理论上二级缓存可以解决循环依赖问题,但是需要注意,为什么需要在三级缓存中存储匿名内部类(ObjectFactory),原因在于 需要创建代理对象 eg:现有A类,需要生成代理对象 A是否需要进行实例化(需要) 在三级缓存中存放的是生成具体对象的一个匿名内部类,该类可能是代理类也可能是普通的对象,而使用三级缓存可以保证无论是否需要是代理对象,都可以保证使用的是同一个对象,而不会出现,一会儿使用普通bean 一会儿使用代理类7、介绍一下Spring bean 的生命周期、注入方式和作用域 难度系数⭐Bean的生命周期1默认情况下IOC容器中bean的生命周期分为五个阶段:调用构造器 或者是通过工厂的方式创建Bean对象给bean对象的属性注入值调用初始化方法进行初始化 初始化方法是通过init-method来指定的.使用IOC容器关闭时 销毁Bean对象.2当加入了Bean的后置处理器后IOC容器中bean的生命周期分为七个阶段:调用构造器 或者是通过工厂的方式创建Bean对象给bean对象的属性注入值执行Bean后置处理器中的 postProcessBeforeInitialization调用初始化方法进行初始化 初始化方法是通过init-method来指定的.x执行Bean的后置处理器中 postProcessAfterInitialization使用IOC容器关闭时 销毁Bean对象只需要回答出第一点即可第二点也回答可适当 加分。注入方式通过 setter 方法注入通过构造方法注入Bean的作用域总共有四种作用域:Singleton 单例的Prototype 原型的RequestSession8、请描述一下Spring 的事务管理 难度系数⭐1声明式事务管理的定义用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。这样的好处是事务管理不侵入开发的组件具体来说业务逻辑对象就不会意识到正在事务管理之中事实上也应该如此因为事务管理是属于系统层面的服务而不是业务逻辑的一部分如果想要改变事务管理策划的话也只需要在定义文件中重新配置即可这样维护起来极其方便。基于 TransactionInterceptor 的声明式事务管理两个次要的属性 transactionManager用来指定一个事务治理器 并将具体事务相关的操作请托给它 其他一个是 Properties 类型的transactionAttributes 属性该属性的每一个键值对中键指定的是方法名方法名可以行使通配符 而值就是表现呼应方法的所运用的事务属性。2基于 Transactional 的声明式事务管理Spring 2.x 还引入了基于 Annotation 的体式格式具体次要触及Transactional 标注。Transactional 可以浸染于接口、接口方法、类和类方法上。算作用于类上时该类的一切public 方法将都具有该类型的事务属性。3编程式事物管理的定义在代码中显式挪用 beginTransaction()、commit()、rollback()等事务治理相关的方法 这就是编程式事务管理。Spring 对事物的编程式管理有基于底层 API 的编程式管理和基于 TransactionTemplate 的编程式事务管理两种方式。9、MyBatis中 #{}和${}的区别是什么 难度系数⭐#{}是预编译处理${}是字符串替换Mybatis在处理#{}时会将sql中的#{}替换为?号调用PreparedStatement的set方法来赋值Mybatis在处理${}时就是把${}替换成变量的值使用#{}可以有效的防止SQL注入提高系统安全性。10、Mybatis 中一级缓存与二级缓存 难度系数⭐MyBatis的缓存分为一级缓存和 二级缓存。一级缓存是SqlSession级别的缓存默认开启。二级缓存是NameSpace级别(Mapper)的缓存多个SqlSession可以共享使用时需要进行配置开启。缓存的查找顺序二级缓存 一级缓存 数据库11、MyBatis如何获取自动生成的(主)键值 难度系数⭐在insert标签中使用 useGeneratedKeys和keyProperty 两个属性来获取自动生成的主键值。示例:insert id”insertname” usegeneratedkeys”true” keyproperty”id”insert into names (name) values (#{name})/insertJava12、简述Mybatis的动态SQL列出常用的6个标签及作用 难度系数⭐动态SQL是MyBatis的强大特性之一 基于功能强大的OGNL表达式。动态SQL主要是来解决查询条件不确定的情况在程序运行期间根据提交的条件动态的完成查询常用的标签:if : 进行条件的判断where在if判断后的SQL语句前面添加WHERE关键字并处理SQL语句开始位置的AND 或者OR的问题trim可以在SQL语句前后进行添加指定字符 或者去掉指定字符.set: 主要用于修改操作时出现的逗号问题choose when otherwise类似于java中的switch语句.在所有的条件中选择其一foreach迭代操作13、Mybatis 如何完成MySQL的批量操作 难度系数⭐MyBatis完成MySQL的批量操作主要是通过foreach标签来拼装相应的SQL语句例如:insert** idinsertBatch insert into tbl_employee(last_name,email,gender,d_id) valuesforeach** collectionemps itemcurr_emp separator,**(#{curr_emp.lastName},#{curr_emp.email},#{curr_emp.gender},#{curr_emp.dept.id})/foreach/insertJava14、谈谈怎么理解SpringBoot框架 难度系数⭐⭐Spring Boot 是 Spring 开源组织下的子项目是 Spring 组件一站式解决方案主要是简化了使用 Spring 的难度简省了繁重的配置提供了各种启动器开发者能快速上手。Spring Boot的优点独立运行Spring Boot而且内嵌了各种servlet容器Tomcat、Jetty等现在不再需要打成war包部署到容器中Spring Boot只要打成一个可执行的jar包就能独立运行所有的依赖包都在一个jar包内。简化配置spring-boot-starter-web启动器自动依赖其他组件简少了maven的配置。除此之外还提供了各种启动器开发者能快速上手。自动配置Spring Boot能根据当前类路径下的类、jar包来自动配置bean如添加一个spring-boot-starter-web启动器就能拥有web的功能无需其他配置。无代码生成和XML配置Spring Boot配置过程中无代码生成也无需XML配置文件就能完成所有配置工作这一切都是借助于条件注解完成的这也是Spring4.x的核心功能之一。应用监控Spring Boot提供一系列端点可以监控服务及应用做健康检测。Spring Boot缺点Spring Boot虽然上手很容易但如果你不了解其核心技术及流程所以一旦遇到问题就很棘手而且现在的解决方案也不是很多需要一个完善的过程。15、Spring Boot 的核心注解是哪个 它主要由哪几个注解组成的 难度系数⭐启动类上面的注解是SpringBootApplication它也是 Spring Boot 的核心注解主要组合包含了以下 3 个注解SpringBootConfiguration组合了 Configuration 注解实现配置文件的功能。EnableAutoConfiguration打开自动配置的功能也可以关闭某个自动配置的选项如关闭数据源自动配置功能 SpringBootApplication(exclude { DataSourceAutoConfiguration.class })。ComponentScanSpring组件扫描。16、Spring Boot自动配置原理是什么 难度系数⭐注解 EnableAutoConfiguration, Configuration, ConditionalOnClass 就是自动配置的核心首先它得是一个配置文件其次根据类路径下是否有这个类去自动配置。EnableAutoConfiguration是实现自动配置的注解Configuration表示这是一个配置文件具体参考文档17、SpringBoot配置文件有哪些 怎么实现多环境配置 难度系数⭐Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。application 配置文件这个容易理解主要用于 Spring Boot 项目的自动化配置。bootstrap配置文件的特性bootstrap 由父 ApplicationContext 加载比 applicaton 优先加载bootstrap 里面的属性不能被覆盖bootstrap 配置文件有以下几个应用场景使用 Spring Cloud Config 配置中心时这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息一些固定的不能被覆盖的属性一些加密/解密的场景提供多套配置文件如applcation.propertiesapplication-dev.propertiesapplication-test.propertiesapplication-prod.properties18、SpringBoot和SpringCloud是什么关系 难度系数⭐Spring Boot 是 Spring 的一套快速配置脚手架可以基于Spring Boot 快速开发单个微服务Spring Cloud是一个基于Spring Boot实现的开发工具Spring Boot专注于快速、方便集成的单个微服务个体Spring Cloud关注全局的服务治理框架 Spring Boot使用了默认大于配置的理念很多集成方案已经帮你选择好了能不配置就不配置Spring Cloud很大的一部分是基于Spring Boot来实现必须基于Spring Boot开发。可以单独使用Spring Boot开发项目但是Spring Cloud离不开 Spring Boot。19、SpringCloud都用过哪些组件 介绍一下作用 难度系数⭐Nacos--作为注册中心和配置中心实现服务注册发现和服务健康监测及配置信息统一管理Gateway--作为网关作为分布式系统统一的出入口进行服务路由统一鉴权等OpenFeign--作为远程调用的客户端实现服务之间的远程调用Sentinel--实现系统的熔断限流Sleuth--实现服务的链路追踪20、Nacos作用以及注册中心的原理 难度系数⭐⭐Nacos英文全称Dynamic Naming and Configuration ServiceNa为naming/nameServer即注册中心,co为configuration即注册中心service是指该注册/配置中心都是以服务为核心。Nacos注册中心分为server与clientserver采用Java编写为client提供注册发现服务与配置服务。而client可以用多语言实现client与微服务嵌套在一起nacos提供sdk和openApi如果没有sdk也可以根据openApi手动写服务注册与发现和配置拉取的逻辑。服务注册原理服务注册方法以Java nacos client v1.0.1 为例子服务注册的策略的是每5秒向nacos server发送一次心跳心跳带上了服务名服务ip服务端口等信息。同时 nacos server也会向client 主动发起健康检查支持tcp/http检查。如果15秒内无心跳且健康检查失败则认为实例不健康如果30秒内健康检查失败则剔除实例。21、Feign工作原理 难度系数⭐⭐主程序入口添加了EnableFeignClients注解开启对FeignClient扫描加载处理。根据Feign Client的开发规范定义接口并加FeignClient注解。当程序启动时会进行包扫描扫描所有FeignClient的注解的类并且讲这些信息注入Spring IOC容器中当定义的的Feign接口中的方法被调用时通过JDK的代理方式来生成具体的RequestTemplate.当生成代理时Feign会为每个接口方法创建一个RequestTemplate。当生成代理时Feign会为每个接口方法创建一个RequestTemplate对象该对象封装HTTP请求需要的全部信息如请求参数名请求方法等信息都是在这个过程中确定的。然后RequestTemplate生成Request,然后把Request交给Client去处理这里指的时Client可以时JDK原生的URLConnection,Apache的HttpClient,也可以时OKhttp最后Client被封装到LoadBalanceClient类这个类结合Ribbon负载均衡发器服务之间的调用。第四章-MySQL篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho1、Select 语句完整的执行顺序 难度系数⭐SQL Select 语句完整的执行顺序1from 子句组装来自不同数据源的数据2where 子句基于指定的条件对记录行进行筛选3group by 子句将数据划分为多个分组4使用聚集函数进行计算5使用 having 子句筛选分组6计算所有的表达式7select 的字段8使用order by 对结果集进行排序。2、MySQL事务 难度系数⭐⭐事务的基本要素ACID原子性Atomicity事务开始后所有操作要么全部做完要么全部不做不可能停滞在中间环节。事务执行过程中出错会回滚到事务开始前的状态所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体就像化学中学过的原子是物质构成的基本单位一致性Consistency事务开始前和结束后数据库的完整性约束没有被破坏 。比如A向B转账不可能A扣了钱B却没收到。隔离性Isolation同一时间只允许一个事务请求同一数据不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱在A取钱的过程结束前B不能向这张卡转账。持久性Durability事务完成后事务对数据库的所有更新将被保存到数据库不能回滚。MySQL事务隔离级别事务隔离级别脏读不可重复读幻读读未提交read-uncommitted是是是读提交read-committed否是是可重复读repeatable-read否否是串行化serializable否否否事务的并发问题脏读事务A读取了事务B更新的数据然后B回滚操作那么A读取到的数据是脏数据不可重复读事务 A 多次读取同一数据事务 B 在事务A多次读取的过程中对数据作了更新并提交导致事务A多次读取同一数据时结果 不一致幻读系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级但是系统管理员B就在这个时候插入了一条具体分数的记录当系统管理员A改结束后发现还有一条记录没有改过来就好像发生了幻觉一样这就叫幻读。如何解决脏读、幻读、不可重复读脏读 隔离级别为 读提交、可重复读、串行化可以解决脏读不可重复读隔离级别为可重复读、串行化可以解决不可重复读幻读隔离级别为串行化可以解决幻读、通过MVCC 区间锁可以解决幻读小结不可重复读的和幻读很容易混淆不可重复读侧重于修改幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行解决幻读需要锁表3、MyISAM和InnoDB的区别 难度系数⭐MyISAMInnoDB事务不支持支持锁表锁表锁、行锁文件存储3个1个外键不支持支持4、悲观锁和乐观锁的怎么实现 难度系数⭐⭐悲观锁select...for update是MySQL提供的实现悲观锁的方式。例如select price from item where id100 for update此时在items表中id为100的那条数据就被我们锁定了其它的要执行select price from items where id100 for update的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。MySQL有个问题是select...for update语句执行中所有扫描过的行都会被锁上因此在MySQL中用悲观锁务必须确定走了索引而不是全表扫描否则将会将整个数据表锁住。乐观锁乐观锁相对悲观锁而言它认为数据一般情况下不会造成冲突所以在数据进行提交更新的时候才会正式对数据的冲突与否进行检测如果发现冲突了则让返回错误信息让用户决定如何去做。利用数据版本号version机制是乐观锁最常用的一种实现方式。一般通过为数据库表增加一个数字类型的 “version” 字段当读取数据时将version字段的值一同读出数据每更新一次对此version值1。当我们提交更新的时候判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对如果数据库表当前版本号与第一次取出来的version值相等则予以更新否则认为是过期数据返回更新失败。举例//1: 查询出商品信息select (quantity,version) from items where id100;//2: 根据商品信息生成订单insert into orders(id,item_id) values(null,100);//3: 修改商品的库存update items set quantityquantity-1,versionversion1 where id100 and version#{version};5、聚簇索引与非聚簇索引区别 难度系数⭐⭐都是B树的数据结构聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的找到索引也就找到了数据数据的物理存放顺序与索引顺序是一致的即:只要索引是相邻的那么对应的数据一定也是相邻地存放在磁盘上的非聚簇索引叶子节点不存储数据、存储的是数据行地址也就是说根据索引查找到数据行的位置再取磁盘查找数据这个就有点类似一本书的目录比如我们要找第三章第一节那我们先在这个目录里面找找到对应的页码后再去对应的页码看文章。优势:1、查询通过聚簇索引可以直接获取数据相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效率要高2、聚簇索引对于范围查询的效率很高因为其数据是按照大小排列的3、聚簇索引适合用在排序的场合非聚簇索引不适合劣势;1、维护索引很昂贵特别是插入新行或者主键被更新导至要分页(pagesplit)的时候。建议在大量插入新行后选在负载较低的时间段通过OPTIMIZETABLE优化表因为必须被移动的行数据可能造成碎片。使用独享表空间可以弱化碎片2、表因为使用uuId(随机ID)作为主键使数据存储稀疏这就会出现聚簇索引有可能有比全表扫面更慢所以建议使用int的auto_increment作为主键3、如果主键比较大的话那辅助索引将会变的更大因为辅助索引的叶子存储的是主键值过长的主键值会导致非叶子节点占用占用更多的物理空间6、什么情况下mysql会索引失效 难度系数⭐失效条件where 后面使用函数使用or条件模糊查询 %放在前边类型转换组合索引 最佳左前缀匹配原则#查询条件用到了计算或者函数explain SELECT * from test_slow_query where age 20explain SELECT * from test_slow_query where age 10 30#模糊查询EXPLAIN SELECT * from test_slow_query where NAME like %吕布EXPLAIN SELECT * from test_slow_query where NAME like %吕布%EXPLAIN SELECT * from test_slow_query where NAME like 吕布#用到了or条件EXPLAIN SELECT * from test_slow_query where NAME 吕布 or name aaa#类型不匹配查询explain SELECT * from test_slow_query where NAME 11explain SELECT * from test_slow_query where NAME 11SQL7、Btree 与 B-tree区别 难度系数⭐⭐原理:分批次的将磁盘块加载进内存中进行检索,若查到数据,则直接返回,若查不到,则释放内存,并重新加载同等数据量的索引进内存,重新遍历结构: 数据 向下的指针 指向数据的指针特点:1节点排序2 .一个节点了可以存多个元索多个元索也排序了结构: 数据 向下的指针特点:1.拥有B树的特点2.叶子节点之间有指针3.非叶子节点上的元素在叶子节点上都冗余了也就是叶子节点中存储了所有的元素并且排好顺序从结构上看,BTree 相较于 B-Tree 而言 缺少了指向数据的指针 也就红色小方块;Mysq|索引使用的是B树因为索引是用来加快查询的而B树通过对数据进行排序所以是可以提高查询速度的然后通过一个节点中可以存储多个元素从而可以使得B树的高度不会太高在Mysql中一个Innodb页就是一个B树节点一个Innodb页默认16kb所以一般情况下一颗两层的B树可以存2000万行左右的数据然后通过利用B树叶子节点存储了所有数据并且进行了排序并且叶子节点之间有指针可以很好的支持全表扫描范围查找等SQL语句8、以MySQL为例Linux下如何排查问题 难度系数⭐⭐类似提问方式:如果线上环境出现问题比如网站卡顿重则瘫痪 如何是好?---linux---mysql/redis/nacos/sentinel/sluth---可以从以上提到的技术点中选择一个自己熟悉单技术点进行分析以mysql为例1,架构层面 是否使用主从2,表结构层面 是否满足常规的表设计规范(大量冗余字段会导致查询会变得很复杂)3,sql语句层面(⭐)前提:由于慢查询日志记录默认是关闭的,所以开启数据库mysql的慢查询记录 的功能 从慢查询日志中去获取哪些sql语句时慢查询 默认10S ,从中获取到sql语句进行分析3.1 explain 分析一条sqlId:执行顺序 如果单表的话,无参考价值 如果是关联查询,会据此判断主表 从表Select_type:simpleTable:表Type: ALL 未创建索引 、const、 常量ref其他索引 、eq_ref 主键索引、Possible_keysKey 实际是到到索引到字段Key_len 索引字段数据结构所使用长度 与是否有默认值null 以及对应字段到数据类型有关有一个理论值 有一个实际使用值也即key_len的值Rows 检索的行数 与查询返回的行数无关Extra 常见的值usingfilesort 使用磁盘排序算法进行排序事关排序 分组 的字段是否使用索引的核心参考值还可能这样去提问sql语句中哪些位置适合建索引/索引建立在哪个位置Select id,name,age from user where id1 and name”xxx” order by age总结: 查询字段 查询条件(最常用) 排序/分组字段补充:如何判断是数据库的问题?可以借助于top命令9、如何处理慢查询 难度系数⭐⭐在业务系统中除了使用主键进行的查询其他的都会在测试库上测试其耗时慢查询的统计主要由运维在做会定期将业务中的慢查询反馈给我们。慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是加载了不需要的数据列?还是数据量太大?所以优化也是针对这三个方向来的首先分析语句看看是否加载了额外的数据可能是查询了多余的行并且抛弃掉了可能是加载了许多结果中并不需要的列对语句进行分析以及重写。分析语句的执行计划然后获得其使用索引的情况之后修改语句或者修改索引使得语句可以尽可能的命中索引。如果对语句的优化已经无法进行可以考虑表中的数据量是否太大如果是的话可以进行横向或者纵向的分表。具体处理流程 阿里云RDS为例1.开启慢查询设置日志管理导出慢查询文件测试环境通过explain执行sql主要关心以下字段type连接类型key MYSQL使用的索引rows显示MYSQL执行查询的行数简单且重要数值越大越不好说明没有用好索引extra该列包含MySQL解决查询的详细信息。篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho10、数据库分表操作 难度系数⭐水平分表步长法1000万一张表拆分取模法举例根据用户id取模落入不能的表垂直分表大表拆小表。商品信息 spu_info spu_image ...可以说使用Mycat或者ShardingSphere等中间件来做具体怎么做就要结合具体的场景进行分析了。可以参考MySQL分库分表写得太好了-mysql分库分表11、MySQL优化 难度系数⭐1尽量选择较小的列2将where中用的比较频繁的字段建立索引3select子句中避免使用‘*’4避免在索引列上使用计算、not in 和等操作5当只需要一行数据的时候使用limit 16保证单表数据不超过200W适时分割表。针对查询较慢的语句可以使用explain 来分析该语句具体的执行情况。7避免改变索引列的类型。8选择最有效的表名顺序from字句中写在最后的表是基础表将被最先处理在from子句中包含多个表的情况下你必须选择记录条数最少的表作为基础表。9避免在索引列上面进行计算。10尽量缩小子查询的结果12、SQL语句优化案例 难度系数⭐例1where 子句中可以对字段进行 null 值判断吗可以比如 select id from t where num is null 这样的 sql 也是可以的。但是最好不要给数据库留NULL尽可能的使用 NOT NULL 填充数据库。不要以为 NULL 不需要空间比如char(100) 型在字段建立时空间就固定了 不管是否插入值NULL 也包含在内都是占用 100 个字符的空间的如果是 varchar 这样的变长字段null 不占用空间。可以在 num 上设置默认值 0确保表中 num 列没有 null 值然后这样查询select id from t where num 0。例2如何优化?下面的语句select * from admin left join log on admin.admin_id log.admin_id where log.admin_id10优化为select * from (select * from admin where admin_id10) T1 lef join log on T1.admin_id log.admin_id。使用 JOIN 时候应该用小的结果驱动大的结果left join 左边表结果尽量小如果有条件应该放到左边先处理 right join 同理反向同时尽量把牵涉到多表联合的查询拆分多个 query多个连表查询效率低容易到之后锁表和阻塞。例3limit 的基数比较大时使用 between例如select * from admin order by admin_id limit 100000,10优化为select * from admin where admin_id between 100000 and 100010 order by admin_id。例4尽量避免在列上做运算这样导致索引失效例如select * from admin where year(admin_time)2014优化为 select * from admin where admin_time 2014-01-01′13、你们公司有哪些数据库设计规范 难度系数⭐一基础规范1、表存储引擎必须使用InnoD表字符集默认使用utf8必要时候使用utf8mb4解读1通用无乱码风险汉字3字节英文1字节2utf8mb4是utf8的超集有存储4字节例如表情符号时使用它2、禁止使用存储过程视图触发器Event解读1对数据库性能影响较大互联网业务能让站点层和服务层干的事情不要交到数据库层2调试排错迁移都比较困难扩展性较差3、禁止在数据库中存储大文件例如照片可以将大文件存储在对象存储系统数据库中存储路径4、禁止在线上环境做数据库压力测试5、测试开发线上数据库环境必须隔离二命名规范1、库名表名列名必须用小写采用下划线分隔解读abcAbcABC都是给自己埋坑2、库名表名列名必须见名知义长度不要超过32字符解读tmpwushan谁知道这些库是干嘛的3、库备份必须以bak为前缀以日期为后缀4、从库必须以-s为后缀5、备库必须以-ss为后缀三表设计规范1、单实例表个数必须控制在2000个以内2、单表分表个数必须控制在1024个以内3、表必须有主键推荐使用UNSIGNED整数为主键潜在坑删除无主键的表如果是row模式的主从架构从库会挂住4、禁止使用外键如果要保证完整性应由应用程式实现解读外键使得表之间相互耦合影响update/delete等SQL性能有可能造成死锁高并发情况下容易成为数据库瓶颈5、建议将大字段访问频度低的字段拆分到单独的表中存储分离冷热数据四列设计规范1、根据业务区分使用tinyint/int/bigint分别会占用1/4/8字节2、根据业务区分使用char/varchar解读1字段长度固定或者长度近似的业务场景适合使用char能够减少碎片查询性能高2字段长度相差较大或者更新较少的业务场景适合使用varchar能够减少空间3、根据业务区分使用datetime/timestamp解读前者占用5个字节后者占用4个字节存储年使用YEAR存储日期使用DATE存储时间使用datetime4、必须把字段定义为NOT NULL并设默认值解读1NULL的列使用索引索引统计值都更加复杂MySQL更难优化2NULL需要更多的存储空间3NULL只能采用IS NULL或者IS NOT NULL而在/!/in/not in时有大坑5、使用INT UNSIGNED存储IPv4不要用char(15)6、使用varchar(20)存储手机号不要使用整数解读1牵扯到国家代号可能出现/-/()等字符例如862手机号不会用来做数学运算3varchar可以模糊查询例如like ‘138%’7、使用TINYINT来代替ENUM解读ENUM增加新值要进行DDL操作五索引规范1、唯一索引使用uniq_[字段名]来命名2、非唯一索引使用idx_[字段名]来命名3、单张表索引数量建议控制在5个以内解读1互联网高并发业务太多索引会影响写性能2生成执行计划时如果索引太多会降低性能并可能导致MySQL选择不到最优索引3异常复杂的查询需求可以选择ES等更为适合的方式存储4、组合索引字段数不建议超过5个解读如果5个字段还不能极大缩小row范围八成是设计有问题5、不建议在频繁更新的字段上建立索引6、非必要不要进行JOIN查询如果要进行JOIN查询被JOIN的字段必须类型相同并建立索引解读踩过因为JOIN字段类型不一致而导致全表扫描的坑么7、理解组合索引最左前缀原则避免重复建设索引如果建立了(a,b,c)相当于建立了(a), (a,b), (a,b,c)