当前位置: 首页 > news >正文

莆田网站 建设建站之星如何建网站

莆田网站 建设,建站之星如何建网站,网站建设 管理系统开发,网站建设 嘉定文章目录前言1、JDK1.8 的新特性有哪些#xff1f;2、JDK 和 JRE 有什么区别#xff1f;3、String#xff0c;StringBuilder#xff0c;StringBuffer 三者的区别#xff1f;4、为什么 String 拼接的效率低#xff1f;5、ArrayList 和 LinkedList 有哪些区别#xff1f;6… 文章目录前言1、JDK1.8 的新特性有哪些2、JDK 和 JRE 有什么区别3、StringStringBuilderStringBuffer 三者的区别4、为什么 String 拼接的效率低5、ArrayList 和 LinkedList 有哪些区别6、CopyOnWriteArrayList 的底层原理7、ArrayList 的扩容机制原理8、Map 集合的遍历方式有哪些9、Map 集合的特点10、HashMap和Hashtable的区别是?11、HashMap 的扩容机制原理12、ConcurrentHashMap 的扩容机制原理13、ConcurrentHashMap 是如何保证线程安全的14、HashCode()、equals() 的区别15、HashSet 保证元素唯一性的原理16、ThreadLoacl 的底层原理17、如何理解 volatile 关键字18、什么是 CAS19、什么是 AQS20、ReentrantLock 中的公平锁和非公平锁的底层实现21、ReentrantLock 中的 tryLock() 和 lock() 方法的区别22、CountDownLatch 和 Semaphore 的区别和底层原理23、Synchronized 的偏向锁、轻量级锁、重量级锁24、Synchronized 和 ReentrantLock 的区别25、Lock 接口比 synchronized 的优势是什么26、创建线程的方式有哪些27、线程池的底层工作原理28、JVM 中哪些是线程共享区29、JVM 中哪些可以作为 gc root ?30、JVM 的内存模型是怎样的31、JVM 为什么使用元空间替换了永久代32、项目如何排查 JVM 问题33、说说类加载器双亲委派模型34、Tomcat 中为什么要使用自定义类加载器35、Tomcat 如何进行优化36、游览器发出一个请求到收到响应经历了哪些步骤37、跨域请求是什么有什么问题怎么解决38、谈谈你对 Spring 的理解39、Spring 中的 Bean 的生命周期40、IOC 和 DI 是什么41、Spring IOC 的工作流程42、Spring AOP 的实现原理43、谈谈你对 Spring MVC 的理解44、Spring MVC 的工作流程45、Spring 中 Bean 是线程安全的吗46、Spring 中 Bean 的作用域有哪些47、ApplicationContext 和 BeanFactory 有什么区别48、Spring 中的事务是如何实现的49、Spring 中什么时候 Transactional 会失效50、Spring 容器启动流程是怎样的51、Spring 用到了哪些设计模式52、Spring 如何解决循环依赖问题53、Spring 里面的事务和分布式事务的使用如何区分54、Spring Boot 中常用注解以及其底层实现55、Spring Boot 自动装配机制的原理56、Spring Boot 是如何启动 Tomcat 的57、SpringBoot 中配置文件的加载顺序是怎样的58、什么是 CAP 理论59、什么是 BASE 理论60、什么时 RPC61、分布式 ID 是什么有哪些解决方案62、分布式锁的使用场景是什么有哪些实现方案63、什么是分布式事务有哪些实现方案64、雪花算法的实现原理65、什么是 ZAB 协议66、为什么 Zookeeper 可以用来作为注册中心67、Zookeeper 中的领导者选举的流程是怎样的68、Zookeeper 集群中节点之间数据是如何同步的69、Dubbo 支持哪些负载均衡策略70、Dubbo 是如何完成服务导出的71、Dubbo 是如何完成服务引入的72、Dubbo 的架构设计是怎样的73、谈谈你对 Spring Cloud 的理解74、Spring Cloud 有哪些常用组件作用是什么75、Spring Cloud 和 Dubbo 有哪些区别76、什么是服务雪崩什么是服务限流77、什么是服务熔断什么是服务降级区别是什么78、SOA、分布式、微服务之间有什么关系和区别79、BIO、NIO、AIO 分别是什么80、零拷贝是什么81、Netty 是什么和 Tomcat 有什么区别特点是什么82、Netty 的线程模型是怎么样的83、Netty 的高性能体现在哪些方面84、Redis 有哪些数据结构分别有哪些典型的应用场景85、Redis 分布式锁底层是如何实现的86、Redis 主从复制的核心原理87、缓存穿透、缓存击穿、缓存雪崩分别是什么88、Redis 和 MySql 如何保证数据一致89、Explain 语句结果中各个字段分别表示什么90、索引覆盖是什么91、最左前缀原则是什么92、Innodb 是如何实现事务的93、B树和B树的区别为什么 MySql 使用B树94、MySql 锁有哪些如何理解94、MySql 慢查询该如何优化95、消息队列有哪些作用96、死信队列是什么延时队列是什么97、Kafka 为什么比 RocketMQ 的吞吐量要高98、Kafka 的 Pull 和 Push 分别有什么优缺点99、RocketMQ 的底层实现原理100、消息队列如何保证消息可靠传输101、TCP 的三次握手和四次挥手前言 以下面试题是我之前面试时所遇到的问题以及以下三个B站UP主分享的面试题总结而来 【Java最全面试攻略】-- 周瑜 【面试突击班】-- 左神 【Java面试】-- Mic 有时间的话可以去看看以上 UP 主分享的原视频我这里只是做了一点点搬运的工作。 1、JDK1.8 的新特性有哪些 Lambda 表达式函数式接口方法引用和构造器调用Stream API接口中的默认方法和静态方法新时间日期 API 2、JDK 和 JRE 有什么区别 JDK 是 Java 的开发工具包而 JRE 是 Java 的运行环境JDK 中包含 JREJDK 中有一个名为 jre 的目录里面包含两个文件夹 bin 和 libbin 就是 JVMlib 就是 JVM 工作所需要的类库。 3、StringStringBuilderStringBuffer 三者的区别 String 和 StringBuilder、StringBuffer 的本质区别是String 是一个不可改变的字符序列而StringBuilder和StringBuffer是一个可以改变的字符序列StringBuilder 和 StringBuffer 的功能完全一致不同点在于 StringBuffer 是 JDK1.0 出现的线程安全同步但是效率低而 StringBuilder 是 JDK1.5 出现的线程不安全不同步但是效率高 4、为什么 String 拼接的效率低 因为字符串在用 “” 号做拼接的时候每一次都会产生新的字符串。字符串串联是通过 StringBuilder或 StringBuffer类及其 append() 方法实现的字符串转换是通过 toString() 方法实现的。 5、ArrayList 和 LinkedList 有哪些区别 底层数据结构不同ArrayList 底层是基于数组实现的LinkedList 底层是基于链表实现的查询、添加、删除的时间复杂度不同ArrayList 更适合随机查找LinkedList 更适合删除和添加ArrayList 和 LinkedList 都实现了 List 接口但是 LinkedList 还额外实现了 Deque 接口所以 LinkedList 还可以当作队列来使用 6、CopyOnWriteArrayList 的底层原理 相比 ArrayList CopyOnWriteArrayList 是线程安全的以下是 CopyOnWriteArrayList 的 add 方法源码 public boolean add(E e) {final ReentrantLock lock this.lock;lock.lock();try {Object[] elements getArray();int len elements.length;Object[] newElements Arrays.copyOf(elements, len 1);newElements[len] e;setArray(newElements);return true;} finally {lock.unlock();}}首先 CopyOnWriteArrayList 内部也是用数组来实现的在向 CopyOnWriteArrayList 添加元素时会复制一个新的数组写操作在新数组上进行读操作在原数组上进行并且写操作会加锁防止出现并发写入丢失数据的问题写操作结束之后会把原数组指向新数组CopyOnWriteArrayList 允许在写操作时来读取数据大大提高了读的性能因此适合读多写少的应用场景但是 CopyOnWriteArrayList 会比较占内存同时可能读到的数据不是实时最新的数据所以不适合实时性要求很高的场景 7、ArrayList 的扩容机制原理 源码 /*** Increases the capacity to ensure that it can hold at least the* number of elements specified by the minimum capacity argument.** param minCapacity the desired minimum capacity*/private void grow(int minCapacity) {// 获取到原本的长度int oldCapacity elementData.length;// 新的长度 原本的长度 原本的长度右移一位就是除以2// 所以新的长度就相当于原有长度的 1.5 倍int newCapacity oldCapacity (oldCapacity 1);// 如果新的长度 集合的长度if (newCapacity - minCapacity 0)// 那新的长度就赋值为传进来的newCapacity minCapacity;// 判断数据大小是否超过默认的最大值 if (newCapacity - MAX_ARRAY_SIZE 0)// 如果超过就会做一个处理newCapacity hugeCapacity(minCapacity);// 将原本的数据拷贝到新的数组里面elementData Arrays.copyOf(elementData, newCapacity);}ArrayList 是一个数组结构的存储容器默认情况下数组的长度是10个当然我们可以在构建 ArrayList 的时候指定初始长度随着在程序里面不断的往 ArrayList 里面添加数据当添加的数据达到10个的时候ArrayList 里面就没有足够的容量去存储后续的数据那么这个时候 ArrayList 就会触发自动扩容扩容的流程也很简单首先创建一个新的数据这个新数组的长度是原数组长度的 1.5 倍然后使用 Arrays.copyOf 方法把老数组里面的数据拷贝到新数组里面扩容完成以后再把当前需要添加的元素加入到新的数组里面从而去完成动态扩容这样一个过程。 8、Map 集合的遍历方式有哪些 Testpublic void fun() {MapString,String map new HashMap();map.put(A001,米大傻);map.put(A002,曹大力);map.put(A003,张大仙);map.put(A004,杨大壮);/*** 方式一根据键找值的方式遍历集合* public SetK keySet()将 Map 所有的 key 封装到一个 Set 的集合* public V get(Object key)根据 key(键) 获取 Map 中对应的 value(值)*/SetString set map.keySet();for (String key : set) {String value map.get(key);System.out.println(key: key value: value);}/*** 方式二获取键值对对象集合迭代器遍历集合获取键和值* public SetMap.EntryK,V entrySet()获取所有的键值对对象集合* IteratorE iterator()获取迭代器*/SetMap.EntryString, String entrySet map.entrySet();IteratorMap.EntryString, String iterator entrySet.iterator();while (iterator.hasNext()) {Map.EntryString, String entry iterator.next();String key entry.getKey();String value entry.getValue();System.out.println(key: key value: value);}/*** 方式三获取键值对对象集合增强 for 遍历集合获取键和值* public SetMap.EntryK,V entrySet()获取所有的键值对对象集合*/SetMap.EntryString, String entries map.entrySet();for (Map.EntryString, String entry : entries) {String key entry.getKey();String value entry.getValue();System.out.println(key: key value: value);}/*** 方式四拿到 Map 集合中所有的值* public Collectionv values()将 Map 中所有的 value 封装到一个 Collection 体系的集合*/CollectionString values map.values();for (String value : values) {System.out.println(value: value);}}总结 方式一根据键找值方式遍历方式二获取所有的键值对对象集合通过迭代器遍历方式三获取所有的键值对对象集合通过增强for遍历方式四通过Map集合中values方法拿到所有的值 9、Map 集合的特点 Map 是一个双列集合将键映射到值的对象Map 集合的数据结构只针对健有效跟值没有关系一个映射不能包含重复的键每个键最多只能映射到一个值 10、HashMap和Hashtable的区别是? HashMap 是 JDK1.2 版本出现的允许存储 null 键和 null 值 不同步线程不安全效率高 Hashtable 是 JDK1 .0版本出现的不允许存储 null 键和 null 值 同步线程安全的效率低 11、HashMap 的扩容机制原理 HashMap 上面的数据过多时查询的效率就会变低这时就需要进行扩容以便在存储更多数据的同时也能保证较高的运行效率衡量 HashMap 扩容的标准是负载因子当数据大于容量与负载因子0.75f的乘积时HashMap 就会进行扩容它的扩容机制跟 JDK 的版本有关 JDK 1.7 版本该版本 HashMap 的底层是数组链表 先生成新数组遍历老数组中的每个位置上的链表上的每个元素取每个元素的 key并基于新数组长度计算出每个元素在新数组中的下标将元素添加到新数组中去所有元素转移完之后将新数组赋值给 HashMap 对象的 table 属性 JDK 1.8 版本该版本 HashMap 的底层是数组链表红黑树 先生成新数组遍历老数组中的每个位置上的链表或红黑树如果是链表则直接将链表中的每个元素重新计算下标并添加到新数组中去如果是红黑树则先遍历红黑树先计算出红黑树中每个元素对应在新数组中的下标位置 统计每个下标位置的元素个数如果该位置下的元素个数超过了 8 则生成一个新得到红黑树并将根节点添加到新数组对应的位置如果该位置下的元素个数没有超过 8那么则生成一个链表并将链表的头节点添加到新数组的对应位置 所有元素转移完了之后将新数组赋值给 HashMap 对象的 table 属性 12、ConcurrentHashMap 的扩容机制原理 JDK 1.7 版本 该版本的 ConcurrentHashMap 是基于 Segment 分段实现的每个 Segment 相当于一个小型的 HashMap每个 Segment 内部会进行扩容和 HashMap 的扩容逻辑类似先生成新的数组然后转移元素到新数组中 JDK 1.8 版本 该版本的 ConcurrentHashMap 不再基于 Segment 实现当每个线程进行 put 时如果发现 ConcurrentHashMap 正在进行扩容那么该线程一起进行扩容如果某个线程 put 时发现没有正在进行扩容则将 key-value 添加到 ConcurrentHashMap 中然后判断是否超过阈值超过了则进行扩容ConcurrentHashMap 是支持多个线程同时扩容的扩容之前先生成一个新的数组在转移元素时先将原数组分组将每组分给不同的线程来进行元素的转移每个线程负责一组或多组的元素转移工作 13、ConcurrentHashMap 是如何保证线程安全的 JDK 1.7 版本 ConcurrentHashMap 在 JDK 1.7 版本中使用的是数组链表结构其中数组分为两大类大数组是 Segment小数组是 HashEntry 而加锁是通过 Segment 添加 ReentrantLock 重入锁来保证线程安全的JDK 1.8 版本 ConcurrentHashMap 在 JDK 1.8 版本中使用的是数组链表红黑树的方式实现的它是通过 CAS 或者是 synchronized 来保证线程安全的并且缩小了锁的粒度查询性能也得到了进一步的提升 14、HashCode()、equals() 的区别 hashCode() 和 equals() 都是 Object 类中的方法 如果类中不重写此方法 hashCode()属于是本地方法返回的是对象的地址值equals()比较的是两个对象中成员信息是否相同 如果类中重写此方法 hashCode()返回的是根据对象的成员变量计算出的一个整数equals()比较的是两个对象中成员信息是否相同 类中重写 hashCode() 和 equals() 比较两个对象是否相等 两个对象通过 equals() 比较是相等的那么 hashCode() 肯定相等也就是 equals() 是绝对可靠的两个对象通过 hashCode() 比较相等但是 equals() 去做比较不一定相等也就是 hashCode() 不是绝对可靠的 15、HashSet 保证元素唯一性的原理 HashSet 内部其实利用了 HashMap 来实现的内部持有一个 HashMapE,Object map 的引用操作 HashSet 实际上底层就是操作这个 map往 HashSet 的集合中添加元素底层就调用了 HashMap 的 put 方法源码如下 这个判断流程是 首先比较对象的哈希值是否相同这个哈希值是根据对象 hashCode() 计算出来的 如果哈希值不同就直接添加到集合中如果哈希值相同继续执行equals() 进行比较 返回的是 true说明元素重复不添加返回的是 false说明元素不重复就添加 如果我们使用 HashSet 集合存储对象想要保证元素的唯一性就必须重写 hashCode() 和 equals() 方法 16、ThreadLoacl 的底层原理 ThreadLocal 是 Java 中所提供的线程本地存储机制可以利用该机制将数据缓存在某个线程内部该线程可以在任意时刻、任意方法中获取缓存的数据ThreadLocal 底层是通过 ThreadLocalMap 来实现的每个 Thread 对象中都存在一个 ThreadLocalMapMap 的 key 为 ThreadLocal 对象Map 的 value 为需要缓存的值如果在线程池中使用 ThreadLocal 会造成内存泄漏因为当 ThreadLocal 对象使用完之后应该要把设置的 keyvalue也就是 Entry 对象进行回收但线程池中的线程不会回收而线程对象是通过强引用指向 ThreadLocalMapThreadLocalMap 也是通过强引用指向 Entry 对象线程不被回收Entry 对象也不会被回收从而出现内存泄漏解决办法是在使用了 ThreadLocal 对象之后手动调用 ThreadLocal 的 remove 方法收到清除 Entry 对象ThreadLocal 经典的应用场景就是连接管理一个线程持有一个连接该连接对象可以在不同的方法之间进行传递线程之间不共享同一个连接 17、如何理解 volatile 关键字 在并发领域中存在三大特性原子性、有序性、可见性。volatile 关键字用来修饰对象的属性在并发环境下可以保证这个属性的可见性对于加了 volatile 关键字的属性在对这个属性进行修改时会直接将 CPU 高级缓存中的数据写回到主内存对这个变量的读取也会直接从主内存中读取从而保证了可见性底层是通过操作系统的内存屏障来实现的由于使用了内存屏障所以会禁止重排所以同时也就保证了有序性在很多并发场景下如果用好 volatile 关键字可以很好的提高执行效率。 18、什么是 CAS CAS 是 Java 中 Unsafe 类里面的一个方法它的全称是叫 CompareAndSwap比较并交换的一个意思它的主要功能是能够去保证在多线程的环境下对于共享变量修改的一个原子性。比方说像这样一个场景 public class Example {private int state 0;public void doSome() {if(state 0) { // 多线程环境中存在原子性问题state 1;// TODO}} }有一个成员变量 state 它的默认值是 0 其中定义了一个方法 doSome()这个方法的逻辑是先判断 state 是否为 0如果为 0 就修改成 1 这个逻辑在单线程的情况下看起来没有任何的问题但是在多线程的环境下会存在原子性的问题因为这里是一个典型的 Read-Write 的一个操作一般情况下会在 doSome 这个方法中去加一个 synchornized 的同步锁来解决这样一个原子性问题但是加同步锁一定会带来性能上的损耗所以对于这一类的场景我们可以使用 CAS 机制来进行优化 public class Example {private volatile int state 0;private static final Unsafe unsafe Unsafe.getUnsafe();private static final long stateOffset;static {try {stateOffset unsafe.ObjectFieldOffset(Example.class.getDeclaredField(state));} catch (Exception e) {throw new Error(e);}}public void doSome() {if(unsafe.compareAndSwapInt(this,stateOffset,0,1)) {// TODO}} }以上是优化之后的代码在 doSome() 这个方法中调用了 Unsafe 类里面的 compareAndSwapInt() 方法来达到同样的目的这个方法有四个参数分别是当前对象实例、成员变量、state、在内存地址中的一个偏移量预期值 0 和期望更改之后的值 1CAS 机制会比较 state 内存地址偏移量对于的值和传入的预期值 0 是否相等如果相等就直接修改内存地址中 state 的值等于 1否则返回 false表示修改失败而这个过程它是一个原子的不会存在任何线程安全的问题CompareAndSwap 是一个 native 方法实际上它最终还是会面临同样的问题就是先从内存地址中读取 state 值然后再去比较最后再去修改这过程不管在什么层面去实现都会存在原子性问题所以在 CompareAndSwap 的底层实现里面如果是在多核的 CPU 环境下会增加一个 lock 指令来对缓存或者总线去加锁从而去保证比较并替换这两个操作的原子性。 CAS 主要是应用在一些并发场景里面比较典型的使用场景有两个① JUC 里面 Atomic 的原子实现比如AtomicInteger 和 AtomicLong 等原子类② 实现多线程对共享资源竞争的互斥特性比如AQS、ConcurrentHashMap 以及 ConcurrentLinkedQueue 等。 19、什么是 AQS AQS 是 AbstractQueuedSynchronizer 类是多线程同步器它是 JUC 包中多个组件的底层实现比如lock、CountDownLatch、Semaphore 都用到了 AQS。从本质上来说AQS 提供了两种锁的机制分别是排他锁和共享锁。所谓排他锁就是存在多个线程去竞争同一共享资源的时候同一个时刻只允许一个线程去访问这样一个共享资源也就是说多个线程中只能有一个线程去获得这样一个锁的资源比如 lock 中的 ReentrantLock 重入锁它的实现就是用到了 AQS 中的一个排它锁的功能。共享锁也称为读锁就是在同一个时刻运行多个线程同时获得这样一个锁的资源比如 CountDownLatch 以及 Semaphore 都用到了 AQS 中的共享锁的功能。 AQS 作为互斥锁来说它的整个设计体系中需要解决三个核心的问题① 互斥变量的设计以及如何保证多线程同时更新互斥变量的时候线程的安全性② 未竞争到锁资源的线程的等待以及竞争到锁的资源释放锁之后的唤醒③ 锁竞争的公平性和非公平性。 AQS 采用了一个 int 类型的互斥变量 state 用来记录锁竞争的一个状态0 表示当前没有任何线程竞争锁资源而大于 0 表示已经有线程正在持有锁资源一个线程获得锁资源时候首先会判断 state 是否等于 0 也就是说它是无锁状态如果是则把这个 state 更新为 1表示占用到锁而这个过程中如果多个线程同时去做这样一个操作就会导致线程安全性问题因此 AQS 采用 CAS 机制去保证 state 互斥变量更新的一个原子性未获取到锁的线程通过 Unsafe 类中的 park 方法去进行阻塞把阻塞的线程按照先进先出的原则去加入到一个双向链表的一个结构中当获得锁资源的线程释在放锁之后会从这样一个双向链表的头部去唤醒下一个等待的线程再去竞争锁。 关于锁竞争的公平性和非公平性的问题AQS 的处理方式是在竞争锁资源时候公平锁需要去判断双向链表中是否有阻塞的线程如果有则需要去排队等待而非公平锁的处理方式是不管双向链表中是否存在等待竞争锁的线程它都会直接去尝试更改互斥变量 state 去竞争锁假设在一个临界点获得锁的线程释放锁此时 state 等于 0而当前的这个线程去抢占锁的时候正好可以把 state 修改为 1 那么这个时候就表示它能拿到锁而这个过程是非公平的。 20、ReentrantLock 中的公平锁和非公平锁的底层实现 首先不管是公平锁还是非公平锁它们的底层实现都会使用 AQS 来进行排队它们的区别在于线程在使用 lock() 方法加锁时如果是公平锁会先检查 AQS 队列是否存在线程在排队如果有线程在排队则当前线程也进行排队如果是非公平锁则不会去检查是否有线程在排队而是直接竞争锁不管是公平锁还是非公平锁一旦没竞争到锁都会进行排队当锁释放时都是唤醒排队在最前面的线程所以非公平锁只是体现了线程加锁阶段而没有体现在线程被唤醒阶段另外ReentrantLock 是可重入锁不管是公平锁还是非公平锁都是可重入的 21、ReentrantLock 中的 tryLock() 和 lock() 方法的区别 tryLock() 表示尝试加锁可能加到也可能加不到该方法不会阻塞线程如果加到锁则返回 true没有加到则返回 falselock() 表示阻塞加锁线程会阻塞直到加到锁方法也没有返回值 22、CountDownLatch 和 Semaphore 的区别和底层原理 CountDownLatch 表示计数器可以给 CountDownLatch 设置一个数字一个线程调用 CountDownLatch 的 await() 将会阻塞其他线程可以调用 CountDownLatch 的 countDown() 方法来对 CountDownLatch 中的数字减一当数字被减成 0 后所有 await 的线程都会被唤醒对于的底层原理就是调用 await() 方法的线程会利用 AQS 排队一旦数字被减为 0 则会将 AQS 中排队的线程依次唤醒。 Semaphore 表示信号量可以设置许可的个数表示同时允许最多多少个线程使用该信号量通过 acquire() 来获取许可如果没有许可可以则线程阻塞并通过 AQS 来排队可以通过 release() 方法来释放许可当某个线程释放了某个许可后会从 AQS 中正在排队的第一个线程开始依次唤醒直到没有空闲许可。 23、Synchronized 的偏向锁、轻量级锁、重量级锁 偏向锁在锁对象的对象头中记录一下当前获取到该锁的线程 ID该线程下次如果又来获取该锁就可以直接获取到了轻量级锁由偏向锁升级而来当一个线程获取到锁后此时这把锁是偏向锁此时如果有第二个线程来竞争锁偏向锁就会升级为轻量级锁之所以叫轻量级锁是为了和重量级锁区分开来轻量级锁底层通过自旋来实现的并不会阻塞线程重量级锁如果自旋次数过多仍然没有获取到锁则会升级为重量级锁重量级锁会导致线程阻塞自旋锁自旋锁就是线程在获取锁的过程中不会去阻塞线程也就无所谓唤醒线程阻塞和唤醒这两个步骤都是需要操作系统去进行的比较消耗时间自旋锁是线程通过 CAS 获取预期的一个标记如果没有获取到则继续循环获取如果获取到了则表示获取到了锁这个过程线程一直在运行中相对而言没有使用太多的操作系统资源比较轻量 24、Synchronized 和 ReentrantLock 的区别 synchronized 是一个关键字ReentrantLock 是一个类synchronized 会自动的加锁和释放锁ReentrantLock 需要程序员手动加锁与释放锁synchronized 的底层是 JVM 层面的锁ReentrantLock 是 API 层面的锁synchronized 是非公平锁ReentrantLock 可以选择公平锁和非公平锁synchronized 锁的是对象锁信息保存在对象头中ReentrantLock 通过代码中 int 类型的 state 标识来标识锁的状态synchronized 底层有一个锁升级的过程 25、Lock 接口比 synchronized 的优势是什么 能够显示获取和释放锁锁的运用更灵活 Lock 中的方法 lock() 方法加锁unlock() 方法释放锁 可以方便地实现公平锁 公平锁表示线程获取锁的顺序是按照线程加锁的顺序来进行分配的即先来先得先进先出顺序非公平锁一种获取锁的抢占式机制是随机拿到锁的和公平锁不一样的是先来的不一定先拿到锁这个方式可能造成某一些线程一直拿不到锁这个结果是不公平的 26、创建线程的方式有哪些 继承 Thread 类 Thread 类本质上是实现了 Runnable 接口的一个实例代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start() 实例方法。start() 方法是一个 native 方法它将启动一个新线程并执行 run() 方法。这种方式实现多线程很简单通过自己创建的类直接 extend Thread并重写 run() 方法就可以启动新线程并执行自己定义的 run() 方法优点代码简单缺点该类无法继承别的类 实现 Runnable 接口 Java 中的类属于单继承如果自己的类已经 extend 另一个类就无法直接 extend Thread 但是一个类继承一个类的同时是可以实现多个接口的优点继承其他类统一实现该接口的实例可以共享资源缺点代码复杂 实现 Callable 接口 实现 Runnable 和实现 Callable 接口的方式基本相同不过 Callable 接口中的 call() 方法有返回值Runnable 接口中的 run() 方法无返回值优点可以获得异步任务的返回值 线程池方式 线程池其实就是一个容纳多个线程的容器其中线程可以重复使用省去了繁琐的创建线程对象操作因为反复创建线程是非常消耗资源的优点实现自动化装配易于管理循环利用资源 27、线程池的底层工作原理 线程池内部是通过队列线程实现的当我们利用线程池执行任务时 如果此时线程池中的数量小于 corePoolSize核心线程数即使线程池中的线程都处于空闲状态也要创建新的线程来处理被添加的任务如果此时线程池中的数量等于 corePoolSize但是缓冲队列 workQueue 未满那么任务会被放入缓冲队列如果此时线程池中的数量大于等于 corePoolSize缓冲队列 workQueue 已满并且线程池中的数量小于 maximumPoolSize最大线程数新建的线程来处理被添加的任务如果此时线程池中的数量大于 corePoolSize缓冲队列 workQueue 已满并且线程池中的数量等于 maximumPoolSize那么通过 handler 所指定的策略来处理此任务当线程池中的线程数量大于 corePoolSize 时如果某线程空闲时间超过 keepAliveTime过期时间线程将被终止。这样线程池可以动态的调整池中的线程数 28、JVM 中哪些是线程共享区 堆区和方法区是所有线程共享的栈、本地方法栈、程序计数器是每个线程独有的 29、JVM 中哪些可以作为 gc root ? JVM 在进行垃圾回收时需要找到 “垃圾” 对象也就是没有被引用的对象但是直接找 “垃圾” 对象是比较耗时的所以反过来先找 “非垃圾” 对象也就是正常对象那么就需要从某些 “根” 开始去找根据这些 “根” 的引用路径找到正常对象而这些 “根” 有一个特征就是它指挥引用其他对象而不会被其他对象引用例如栈中的本地变量、方法区中的静态变量、本地方法栈中的变量正在运行的线程等可以作为 gc root。 30、JVM 的内存模型是怎样的 JDK 1.7 的堆内存模型 Young 年轻区代 Young 区被划分为三部分Eden 区和两个大小严格相同的 Survivor 区其中Survivor 区间中某一时刻只有其中一个是被使用的另外一个留做垃圾收集时复制对象用在 Eden 区间变满的时候GC 就会将存活的对象移到空闲的 Survivor 区间中根据 JVM 的策略在经过几次垃圾收集后仍然存活于 Survivor 的对象将被移动到 Tenured 区间。 Tenured 年老区代 Tenured 区主要保存生命周期长的对象一般是一些老的对象 当一些对象在 Young 复制转移一定的次数以后对象就会被转移到 Tenured 区一般如果系统中用了 application 级别的缓存缓存中的对象往往会被转移到这一区间。 Perm 永久区代 Perm代主要保存 classmethodfiled 对象这部份的空间一般不会溢出除非一次性加载了很多的类不过在涉及到热部署的应用服务器的时候有时候会遇到 java.lang.OutOfMemoryError : PermGen space 的错误造成这个错误的很大原因就有可能是每次都重新部署但是重新部署后类的 class 没有被卸载掉这样就造成了大量的 class 对象保存在了 perm 中这种情况下一般重新启动应 用服务器可以解决问题。 Virtual 区 最大内存和初始内存的差值就是Virtual区 JDK 1.8 的堆内存模型 由上图可以看出jdk1.8的内存模型是由2部分组成年轻代年老代 年轻代Eden 2*Survivor 年老代OldGen 在 JDK1.8中变化最大的 Perm 区用 Metaspace (元数据空间) 进行了替换 需要特别说明的是Metaspace所占用的内存空间不是在虚拟机内部而是在本地内存空间中这也是与1.7的永久代最大的区别所在。 31、JVM 为什么使用元空间替换了永久代 在 JDK 1.7 中方法区的实现是在永久代里面它里面主要存储运行时常量池class 类元信息等等永久代属于 JVM 运行时数据区内的一块内存空间可以通过 -XX:PermSize 参数去设置永久代的大小当内存不够的时候就会触发垃圾回收在 JDK 1.8 中取消了永久代由元空间来实现方法区的数据存储元空间不属于 JVM 内存而是直接使用本地内存因此不需要考虑 GC 的一个问题默认情况下元空间是可以无限制的使用本地内存的但是也可以使用 JVM 参数来限制内存的一个使用大小。 至于为什么使用元空间替换了永久代 在 JDK 1.7 版本里面的永久代内存是有上限的虽然可以通过参数来设置但是 JVM 加载 class 总数大小是很难去确定的所以很容易出现 OOM 的问题但是元空间是存储在本地内存里面内存的上限是比较大的可以很好的去避免这个问题永久代的对象是通过 FullGC 进行垃圾回收的也就是和老年代同时实现垃圾回收替换成元空间以后简化了 FullGC 的过程可以在不进行暂停的情况下去并发的释放类的数据同时也提升了 GC 的性能Oracle 要合并 Hotspot 和 JRockit 的一个代码而 JRockit 里面没有永久代 32、项目如何排查 JVM 问题 对于还在正常运行的系统 可以使用 jmap 来查看 JVM 中各个区域的使用情况可以通过 jstack 来查看线程的运行情况比如哪些线程阻塞、是否出现了死锁可以通过 jstat 命令来查看垃圾回收的情况特别是 FllGC如果发现 FallGC 比较频繁那么就得进行调优了通过各个命令的结果或者 jvisualvm 等工具来进行分析首先初步猜测频繁发送 FullGC 的原因如果频繁发生 FullGC 但是又一直没有出现内存溢出那么表示 FullGC 实际上是回收了很多对象了所以这些对象最好能在 YoungGC 过程中就直接回收掉避免这些对象进入到老年代对于这种情况就要考虑这些存活时间不长的对象是不是比较大导致年轻代放不下直接进入到了老年代尝试加大年轻代的大小如果改完之后FullGC 减少则证明修改有效同时还可以找到占用 CPU 最多的线程定位到具体的方法优化这个方法的执行看是否能避免某些对象的创建从而节省内存 对于已经发生了 OOM 的系统 一般生产系统中都会设置当系统发生了 OOM 时生成当时的 dump 文件-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/usr/local/base我们可以利用 jsisualvm 等工具来分析 dump 文件根据 dump 文件找到异常的实例对象和异常的线程占用 CPU 高定位到具体的代码然后再进行详细的分析和调试 33、说说类加载器双亲委派模型 JVM 中存在三个默认的类加载器① BootstrapClassLoader ② ExtClassLoader ③ AppClassLoader AppClassLoader 的父加载器是 ExtClassLoaderExtClassLoader 的父加载器是 BootstrapClassLoader JVM 在加载一个类时会调用 AppClassLoader 的 loadClass 方法来加载这个类不过在这个方法中会先使用 ExtClassLoader 的 loadClass 方法来加载类同样 ExtClassLoader 的 loadClass 方法中会先使用 BootstrapClassLoader 来加载类如果 BootstrapClassLoader 加载到了就直接成功如果 BootstrapClassLoader 没有加载到那么 ExtClassLoader 就会自己尝试加载该类如果没有加载到那么则会由 AppClassLoader 来加载这个类。 所以双亲委派指的是JVM 在加载类时会委派给 Ext 和 Bootstrap 进行加载如果没有加载到才由自己进行加载。 34、Tomcat 中为什么要使用自定义类加载器 一个 Tomcat 中可以部署多个应用而每个应用中都存在很多类并且各个应用中的类是独立的全类名是可以相同的比如说一个订单系统中的某个类和库存系统中的某个类全类名完全相同一个 Tomcat不管内部部署了多少应用Tomcat 启动之后就是一个 Java 进程也就是一个 JVM所以如果 Tomcat 中只存在一个类加载器比如默认的 AppClassLoader那么全类名相同的类就只能加载一个这是有问题的而在 Tomcat 中会为部署的每个应用都生成一个类加载器名字叫做 WebAppClassLoader 这样 Tomcat 中每个应用就可以使用自己的类加载器去加载自己的类从而达到应用之间的类隔离不出现冲突。另外Tomcat 还利用自定义加载器实现了热加载功能。 35、Tomcat 如何进行优化 对于 Tomcat 调优可以从两个方面进行调整内存和线程。 首先启动 Tomcat实际上就是启动了一个 JVM所以可以按 JVM 调优的方式进行调整从而达到 Tomcat 优化的目的另外 Tomcat 中设计了一些缓存区比如 appReadBufSize、bufferPoolSize 等缓存区来提高吞吐量还可以调整 Tomcat 的线程比如调整 minSpareThreads 参数来改变 Tomcat 空闲时的线程数调整 maxThreads 参数来设置 Tomcat 处理连接的最大线程数并且还可以调整 IO 模型比如使用 NIO、APR 这种相比于 BIO 更加高效的 IO 模型 36、游览器发出一个请求到收到响应经历了哪些步骤 游览器解析用户输入的 URL生成一个 HTTP 格式的请求先根据 URL 域名从本地 hosts 文件查找是否有映射 IP如果没有就将域名发送给电脑所配置的 DNS 进行域名解析得到 IP 地址游览器通过操作系统将请求通过四层网络协议发送出去途中可能会经过各种路由器、交换机、最终达到服务器服务器搜到请求后根据请求所指定的端口将请求传递给绑定了该端口的应用程序应用程序接收到请求数据后按照 http 协议的格式进行解析解析得到所要访问的 servlet然后 servlet 来处理这个请求如果是 SpringMVC 中的 DispathServlet那么则会找到对应的 Controller 中的方法并执行该方法得到结果应用程序得到响应后封装成 HTTP 响应的格式并再次通过网络发送给游览器所在的服务器游览器所在的服务器拿到结果后再传递给游览器游览器则负责解析并渲染 37、跨域请求是什么有什么问题怎么解决 游览器有一个安全机制叫做同源策略同源是指协议、域名、端口都一致如果任意一项不一致就是不同源 跨域是指游览器在发起网络请求时会检查该请求所对应得协议、域名、端口和当前网页是否一致如果不一致则游览器会进行限制。 解决 response 添加 header比如resp.setHeader(Access-Control-Allow-Origin,*); 表示可以访问所有网站不受是否同源的限制jsonp 的方式该技术底层就是基于 script 标签来实现的因为 script 标签是可以跨域的后台自己控制先访问同域名下的接口然后在接口中再去使用 HTTPClient 等工具去调用目标接口网关和第三种方式类似都是交给后台服务来进行跨域访问 38、谈谈你对 Spring 的理解 Spring 的发展历程 2004 年发布了 Spring 的第一个版本从全配置文件的形式发展到现在 6.0 的一个版本支持全注解应用开发对于应用开发效率的提升有着很大的帮助 Spring 的组成 Spring 是一个轻量级的 IOC 和 AOP 容器框架是为 java 应用程序提供基础性服务的一套框架目的是用于简化企业应用程序的开发它使得开发者只需要关系业务需求。常见的配置方式有三种基于 XML 的配置、基于注解的配置、基于 java 的配置。 主要由一下几个模块组成 Spring Core核心类库提供 IOC 服务Spring Context提供框架式的 Bean 访问方式以及企业级功能Spring AOPAOP 服务Spring DAO对 JDBC 的抽象简化了数据访问异常的处理Spring ORM对现有的 ORM 框架的支持Spring Web提供了基本的面向 Web 的综合特性例如多方文件上传Spring MVC提供面向 Web 应用的 Model-View-Controller 实现 Spring 的优点 轻量Spring 是轻量级的基本的版本大约 2MB控制反转Spring 通过控制反转实现了松散耦合对象们给出它们的依赖而不是创建或查找依赖的对象们面向切面编程AOPSpring 支持面向切面的编程并且把应用业务逻辑和系统服务分开容器Spring 包含并管理应用中对象的生命周期和配置MVC 框架Spring 的 Web 框架是个精心设计的框架是 Web 框架的一个很好的替代品事务管理Spring 提供一个持续的事务管理接口异常处理Spring 提供方便的 API 把具体技术相关的异常转化为一致的 unchecked 异常使用的人多 39、Spring 中的 Bean 的生命周期 Spring Bean 的生命周期大致可以分为五个阶段 创建前准备 这个阶段的主要作用是 Bean 在开始加载之前要从上下文和一些配置中去解析并且查找 Bean 有关的扩展实现比如像 init-method 容器初始化 Bean 的时候调用的方法destory-method 容器在销毁 Bean 的时候会调用的一些方法以及 beanFactoryPostProcessor 这一类的 Bean 加载过程中的一些前置和后置的一些处理扩展实现这些类或者配置其实是 Spring 提供给开发者用来实现 Bean 并加载过程中的一些扩展在很多的和 Spring 集成的中间件也比较常见比如说 Dubbo 创建实例化 这个阶段的主要作用是通过反射区创建 Bean 的实例对象并且会扫描和解析 Bean 声明的一些属性 依赖注入 如果被实例化的 Bean 存在依赖其它 Bean 对象的情况则需要对这些依赖的 Bean 进行对象注入比如常见的 Autowired 以及 setter 注入等这样一些配置形式同时在这个阶段会触发一些扩展的调用比如说常见的扩展类 BeanPostProcessors 用来去实现 Bean 初始化前后的扩展回调以及像 BeanFactoryAware 等等 容器缓存 该阶段的主要作用是吧 Bean 保存到容器以及 Spring 的缓存中到这个阶段 Bean 就可以被开发者去使用了这个阶段涉及到一些常见的操作像 init-method属性配置的方法会在这个阶段被调用以及像 BeanPostProcessor 的后置处理器方法也会在这个阶段被触发 销毁实例 当 Spring 的应用上下文被关闭的时候那么这个上下文中所有的 Bean 都会被销毁如果存在 Bean 实现了像 DisposableBean 接口或者配置了 destory-method 属性的一些方法会在这个阶段被调用 40、IOC 和 DI 是什么 IOC控制反转 全称是 Inversion Of Control也就是控制反转将对在自身对象中的一个内置对象的控制反转反转后不再由自己本身的对象进行控制这个内置对象的创建而是由第三方系统去控制这个内置对象的创建。简单来说就是把本来在类内部控制的对象反转到类外部进行创建后注入不再由类本身进行控制这就是IOC的本质。 DI依赖注入 全称为 Dependency Injection 意思是自身对象中的内置对象是通过注入的方式进行创建。 OC 和 DI 的关系 IOC 就是容器DI 就是注入这一行为那么 DI 确实就是 IOC 的具体功能的实现。而 IOC 则是 DI 发挥的平台和空间。所以说IOC 和 DI 即是相辅相成的搭档。最重要的是他们都是为了实现解耦而服务的。 DI 是如何实现的? 赖注入可以通过 setter 方法注入(设值注入)、构造器注入和接口注入三种方式来实现Spring 支持 setter 注入和构造器注入通常使用构造器注入来注入必须的依赖关系对于可选的依赖关系则 setter 注入是更好的选择setter 注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。 41、Spring IOC 的工作流程 IOC 的全称是 Inversion Of Control 也就是控制反转它的核心思想就是把对象的管理权限交给了容器应用程序如果需要使用某个对象的实例直接从 IOC 容器里面去获取这种设计的好处在于降低了程序里面对象与对象之间的耦合性使得程序的整个体系结构变得更加灵活。 Spring 中提供了很多方式去声明 Bean比如说在 XML 配置文件里面通过 bean 的标签或者通过 Service 注解或者通过 Configuration 配置类里面的 Bean 注解去声明等等Spring 在启动的时候回去解析这些 Bean然后保存到 IOC 容器里面 Spring IOC 的工作流程大致可以分为两个阶段 第一个阶段是 IOC 容器初始化阶段这个阶段主要是根据程序里面定义的 XML 或者 注解等 Bean 的声明方式通过解析和加载后生成 BeanDefinition 然后把 BeanDefinition 注册到 IOC 容器里面通过注解或者 xml 声明的 Bean 都会解析得到一个 BeanDefinition 实体这个实例里面会包含 bean 的定义和基本的属性最后把这个 BeanDefinition 保存到一个 Map 集合里面从而去完成 IOC 的一个初始化IOC 容器的作用就是对这些注册的 Bean 的定义信息进行处理和维护它是 IOC 容器控制反转的一个核心第二个阶段是完成 Bean 的初始化和依赖注入进入第二个阶段以后这个阶段会做两个事情第一个是通过反射去针对没有设置 lazy-init 属性的单例 bean 进行初始化第二个是完成 Bean 的依赖注入最后一个阶段就是 Bean 的使用我们可以通过 Autowired 这样一个注解或者通过 BeanFactory.getBean() 从 IOC 容器里面去获取一个指定 Bean 的实例另外针对设置了 lazy-init 属性以及非单例 Bean 的一个实例化是在每一次获取 Bean 对象的时候调用 Bean 的初始化方法来完成实例化的并且 Spring IOC 容器不会去管理这些 Bean 42、Spring AOP 的实现原理 Spring AOP 的面向切面编程是面向对象编程的一种补充用于处理系统中分布的各个模块的橫切关注点比如说事务管理、日志、缓存等。它是使用动态代理实现的在内存中临时为增强某个方法生成一个AOP对象这个对象包含目标对象的所有方法在特定的切入点做了增强处理并回调原来的方法。 Spring AOP 的动态代理主要有两种方式实现JDK 动态代理和 cglib 动态代理。JDK 动态代理通过反射来接收被代理的类但是被代理的类必须实现接口核心是 InvocationHandler 和 Proxy 类。cglib 动态代理的类一般是没有实现接口的类cglib 是一个代码生成的类库可以在运行时动态生成某个类的子类所以CGLIB 是通过继承的方式做的动态代理因此如果某个类被标记为 final那么它是无法使用 CGLIB 做动态代理的。 AOP 能做什么 可以降低模块之间的耦合度使系统容易扩展避免修改业务代码避免引入重复代码更好的代码复用 AOP 怎么用 前置通知某方法调用之前发出通知后置通知某方法完成之后发出通知返回后通知方法正常返回后调用通知。在方法调用后正常退出发出通知异常通知在方法调用时异常退出发出通知抛出异常后通知(After throwing advice)在方法抛出异常退出时执行的通知环绕通知通知包裹在被通知的方法的周围 43、谈谈你对 Spring MVC 的理解 首先 Spring MVC 是属于 Spring Framework 生态里面的一个模块它是在 Servlet 的基础上构建并且使用了 MVC 模式设计的一个 Web 框架它的主要目的是为了简化传统的 Servlet JSP 模式下的 Web 开发方式其次 Spring MVC 的整个架构设计是对 Java Web 里面的 MVC 框架模式做了一些增强和扩展主要体现在几个方面① 把传统 MVC 框架里面的 Controller 控制器做了拆分分成了前端控制器 DispatcherServlet 和后端控制器 Controller② 把 Model 模型拆分成业务层 Service 和数据访问层 Repository③ 在视图层可以支持不同的视图比如Freemark、velocity、JSP 等等。所以 Spring MVC 天生就是为了 MVC 模式而设计的因此在开发 MVC 应用的时候会更加方便和灵活。 Spring MVC 的整体工作流程 游览器的请求首先会经过 Spring MVC 里面的核心控制器 DispatcherServlet它主要是把请求分发到对应的 Controller 里面而 Controller 里面处理业务逻辑之后会返回一个 ModeAndView然后 DispatcherServlet 会去寻找一个或者多个 ViewResolver 的视图解析器找到 ModeAndView 指定的视图并且把数据展示到客户端。 Spring MVC 的优点 它是基于组件技术的。全部的应用对象无论控制器和视图还是业务对象之类的都是 java 组件。并且和 Spring 提供的其他基础结构紧密集成不依赖于 ServletAPI (目标虽是如此但是在实现的时候确实是依赖于 Servlet 的)可以任意使用各种视图技术而不仅仅局限于JSP。比如PDF Excel支持各种请求资源的映射策略它是易于扩展的 44、Spring MVC 的工作流程 用户向服务器发送请求请求被 Spring 前端控制器 DispatcherServlet 捕获DispatcherServlet 对请求 URL 进行解析得到请求资源标识符(URI)。然后根据该 URI调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器)最后以 HandlerExecutionChain 对象的形式返回DispatcherServlet 根据获得的 Handler选择一个合适的 HandlerAdapter适配器。( 附注:如果成功获得HandlerAdapter后此时将开始执行拦截器的 preHandler(…)方法)提取 Request 中的模型数据填充 Handler 入参开始执行 Handler (也就是我们自己写的 Controller )。在填充 Handler的入参过程中根据你的配置Spring 将帮你做一些额外的工作 HttpMessageConveter将请求消息 (如 Json、xml 等数据)转换成一个对象将对象转换为指定的响应信息数据转换对请求消息进行数据转换。如 String 转换成 Integer、Double等数据格式化对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等数据验证验证数据的有效性(长度、格式等)验证结果存储到 BindingResult 或 Error 中 Handler 执行完成后向 DispatcherServlet 返回一个 ModelAndView 对象根据返回的 ModelAndView选择一个适合的 ViewResolver ( 必须是已经注册到Spring容器中的ViewResolver)返回给 DispatcherServletViewResolver 结合 Model和View 来渲染视图将渲染结果返回给客户端 45、Spring 中 Bean 是线程安全的吗 Spring 本身并没有针对 Bean 做线程安全的处理所以 如果 Bean 是无状态的那么 Bean 则是线程安全的如果 Bean 是有状态的那么 Bean 则不是线程安全的另外Bean 是不是线程安全的跟 Bean 的作用域没有关系Bean 的作用域只是表示 Bean 的生命周期范围对于任何生命周期的 Bean 都是一个对象这个对象是不是线程安全的还是得看这个 Bean 对象本身。 46、Spring 中 Bean 的作用域有哪些 singleton (单例模式)使用该属性定义 Bean 时IOC 容器仅创建一个 Bean 实例IOC 容器每次返回的是同一个 Bean 实例prototype ( 原型模式)使用该属性定义 Bean 时IOC 容器可以创建多个 Bean 实例每次返回的都是一个新的实例request (HTTP 请求)该属性仅对 HTTP 请求产生作用使用该属性定义 Bean 时每次 HTTP 请求都会创建一个新的 Bean适用于 WebApplicationContext 环境session(会话)该属性仅用于 HTTP Session同一个 Session 共享一个 Bean 实例。不同 Session 使用不同的实例global-session (全局会话在spring5. x版本中已经移除了)该属性仅用于 HTTP Session 同 session 作用域不同的是所有的 Session 共享一个 Bean 实例 47、ApplicationContext 和 BeanFactory 有什么区别 BeanFactory 是 Spring 中非常核心的组件表示 Bean 工厂可以生成 Bean维护 Bean而 ApplicationContext 继承 BeanFactory所以 ApplicationContext 拥有 BeanFactory 所有的特点也是一个 Bean 工厂但是 ApplicationContext 除开继承了 BeanFactory 之外还继承了诸如 EnvironmentCapable、MessageSource、ApplicationEventPublisher 等接口从而 ApplicationContext 还有获取系统环境变量、国际化、事件发布等功能这是 BeanFactory 所不具备的。 48、Spring 中的事务是如何实现的 Spring 事务底层是基于数据库事务和 AOP 机制的首先对于使用了 Transaction 注解的 BeanSpring 会创建一个代理对象作为 Bean当调用代理对象的方法时会先判断该方法上是否加了 Transaction 注解如果加了那么则利用事务管理器创建一个数据库连接并且修改数据库连接的 autocommit 属性为 false禁止此连接的自动提交这是实现 Spring 事务非常重要的一步然后执行当前方法方法中会执行 sql执行完当前方法后如果没有出现异常就直接提交事务如果出现了异常并且这个异常是需要回滚的就会回滚事务否则仍然提交事务Spring 事务的隔离级别对应的就算数据库的隔离级别Spring 事务的传播机制是 Spring 事务自己实现的也是 Spring 事务中最复杂的Spring 事务的传播机制是基于数据库连接来做的一个数据库连接一个事务如果传播机制配置为需要新开一个事务那么实际上就是先建立一个数据连接在此新数据库连接上执行 sql 49、Spring 中什么时候 Transactional 会失效 因为 Spring 事务是基于代理来实现的所以某个加了 Transaction 的方法只有是被代理对象调用时那么这个注解才会生效所以如果是被代理对象来调用这个方法那么 Transaction 是不会失效的。同时如果某个方法是 private 的那么 Transactional 也会失效因为底层 cglib 是基于父子类来实现的子类是不能重载父类的 private 方法的所以无法很好的利用代理也会导致 Transactional 失效。 50、Spring 容器启动流程是怎样的 在创建 Spring 容器也就是启动 Spring 时首先会进行扫描扫描得到所有的 BeanDefinition 对象并存在一个 Map 中然后筛选出非懒加载的单例 BeanDefinition 进行创建 Bean对于多例 Bean 不需要在启动过程中去进行创建对于多例 Bean 会在每次获取 Bean 时利用 BeanDefinition 去创建利用 BeanDefinition 创建 Bean 就是 Bean 的创建生命周期这期间包括了合并 BeanDefinition、推断构造方法、实例化、属性填充、初始化前、初始化、初始化后等步骤其中AOP 就是发生在初始化后这一步骤中单例 Bean 创建完之后Spring 会发布一个容器启动事件Spring 启动结束在源码中会更复杂比如源码中会提供一些模板方法让子类来实现比如源码中还涉及到一些 BeanFactoryPostProcessor 和 BeanPostProcessor 的注册Spring 的扫描就是通过 BeanFactoryPostProcessor 来实现的依赖注入就是通过 BeanPostProcessor 来实现的在 Spring 启动过程中还回去处理 Import 等注解 51、Spring 用到了哪些设计模式 工厂模式 这个很明显在各种 BeanFactory 以及 ApplicationContext 创建中都用到了 模板模式 这个也很明显在各种 BeanFactory 以及 ApplicationContext 实现中也都用到了 代理模式 在 AOP 实现中用到了 JDK 的动态代理 单例模式 这个比如在创建 Bean 的时候 策略模式 在 spring 中我们可以使用 JdbcTemplate 实现对数据库的 CRUD 操作而在查询时我们可能会用到 RowMapper 接口以及 spring 提供的一个 BeanPropertyRowMappe r的实现类。RowMapper 接口就是规范而我们根据实际业务需求编写的每个实现类都是一个达成目标的策略 观察者模式 spring 在 java EE 应用中创建的 WebApplicationContext 时是通过一个 ContextLoaderListener 监听器实现的。监听器就是观察者模式的具体体现 适配器模式 在 spring- framework 中提供了 spring mvc 的开发包。我们在用spring mvc中它实现控制器方式有很多种。例如我们常用的使用 Controller 注解还有实现 Controller 接口或者实现 HttpRequestHandler 接口等等。而在 DispatcherServlet 中如何处理这三种不同的控制器呢它用到了适配器用于对不同的实现方式适配 装饰者模式 BeanWrapper 委派模式 BeanDefinitionParserDelegate 责任链模式 BeanPostProcessor 52、Spring 如何解决循环依赖问题 循环依赖是指一个或者多个 Bean 实例之间会存在直接或者间接的一个依赖关系构成一个循环调用通常表现为三种形态① 互相依赖也就是 A 依赖 BB 依赖 A② 间接依赖两个或者两个以上的 Bean 存在间接依赖关系造成一个循环调用③ 自我依赖也就是自己依赖自己而造成一个循环依赖。Spring 框架本身也考虑了这方面的问题所以它设计了三级缓存来解决部分循环依赖的问题。 所谓三级缓存其实就是用来存放不同类型的 Bean 第一级缓存存放的是完全初始化好的 Bean这个 Bean 可以直接被使用第二级缓存存放的是原始的 Bean 对象也就是说这个 Bean 里面的属性还没有被进行赋值或者没有被依赖注入第三级缓存存放的是 Bean 工厂的一个对象用来生成原始 Bean 对象并且放入到二级缓存里面 比如Bean A 和 Bean B 之间存在一个循环依赖那么在三级缓存的设计里面首先会初始化 Bean A先把 Bean A 实例化然后把 Bean 包装成 ObjectFactory 对象保存到三级缓存里面接下来 Bean A 开始对它的成员属性 Bean B 进行依赖注入于是开始了初始化 Bean B同样它也会做两件事情创建 Bean B 的实例以及加入到 三级缓存里面然后 Bean B 也开始进行一个依赖注入在三级缓存里面去找 Bean A 的这样一个实例于是完成的 Bean A 的一个依赖注入Bean B 初始化成功以后就会保存到一级缓存里面于是Bean A 同样可以成功拿到 Bean B 的一个实例从而去完成正常的依赖注入。 整个流程看起来有些复杂但是它的核心思想就是把 Bean 的实例化和 Bean 中的属性依赖注入这两个过程分离出来不过需要注意的是 Spring 本身只能解决单实例存在的循环引用问题如果存在以下四种情况则需要人为去干预 多实例的 Setter 注入导致的循环依赖需要把 Bean 改成单例构造器注入导致的循环依赖可以通过 Lazy 注解DependsOn 导致的循环依赖找到注解循环依赖的地方迫使它不循环依赖单例的代理对象 Setter 注入导致的循环依赖 可以使用 Lazy或者使用 DependsOn 注解指定加载先后关系 在实际开发过程中出现循环依赖的根本原因其实还是在代码的一个设计上因为模块的耦合度较高的情况下依赖关系的复杂度一定会增加我们应该尽可能的去从系统设计的角度去考虑模块之间的依赖关系避免循环依赖的问题 简短版 Spring 设计了三级缓存来解决循环依赖的问题第一级缓存里面存储完整的 Bean 实例这些实例是可以直接被使用的第二级缓存里面存储的实例化但是还没有设置属性值 Bean 实例也就是 Bean 里面的依赖注入还没有做第三级缓存是用来存放 Bean 工厂它主要用来生成原始 Bean 对象并且放到第二级缓存里面。三级缓存的核心思想就是把 Bean 的实例化和 Bean 里面的依赖注入进行分离采用一级缓存存储完整的 Bean 实例采用二级缓存来存储不完整的 Bean 实例通过不完整的 Bean 实例作为突破口解决循环依赖问题至于第三级缓存主要是解决代理对象的循环依赖问题。 53、Spring 里面的事务和分布式事务的使用如何区分 Spring 里面并没有提供事务它只是提供了对数据库事务的一个管理的封装我们可以通过声明式事务的配置使得开发人员可以从一些复杂的事务处理里面去脱离出来不需要再去关心连接的获取、连接的关闭、事务的提交、事务的回滚等这样一些操作我们可以更加聚焦在业务的开发层面所以 Spring 里面的事务本质上是数据库层面的一个事务而这种事务管理主要是针对于单个数据库里面的多个数据表的操作它去满足一个事务的 ACID 特性。 而分布式事务是解决多个数据库事务操作的一个数据一致性问题传统的关系数据库不支持跨库的事务操作所以需要引用分布式事务的解决方案而 Spring 里面并没有提供分布式事务的场景支持所以Spring 里面的事务和分布式事务在使用上并没有直接的关联关系但是可以使用一些主流的分布式事务解决框架比如shuo像 Seate 集成到 Spring 生态里面去解决分布式事务的一个问题。 54、Spring Boot 中常用注解以及其底层实现 SpringBootApplication 注解这个注解标识了一个 SpringBoot 工程它实际上是另外三个注解的组合这三个注解是 ① SpringBootConfiguration这个注解实际就是一个 Configuration表示启动类也是一个配置类 ② EnableAutoConfiguration开启自动配置向 Spring 容器中导入一个 Selector用来加载 ClassPath 下 SpringFactories 中所定义的自动配置类将这些自动加载为配置 Bean ③ ComponentScan表示扫描路径因为默认是没有配置实际扫描路径所以 SpringBoot 扫描的路径是启动类所在的当前目录Bean 注解用来定义 Bean类似于 XML 中的 bean 标签Spring 在启动时会对 Bean 注解的方法进行解析将方法的名字作为 beanName并通过执行方法得到 Bean 对象 55、Spring Boot 自动装配机制的原理 自动装配简单来说就是自动去把第三方组件的 Bean 装载到 IOC 容器里面不需要开发人员再去写 Bean 相关的一个配置在 Spring boot 应用里面只需要在启动类上加上 SpringBootApplication 注解就可以去实现自动装配SpringBootApplication 是一个复合型注解真正去实现自动装配的注解是 EnableAutoConfiguration自动装配的实现主要依靠三个核心的关键技术① 引入 Starter 启动依赖组件的时候这个组件里面必须要包含一个 Configuration 配置类而在这个配置类里面需要通过 Bean 这个注解去声明需要装配到 IOC 容器里面的 Bean 对象② 这个配置类是放在第三方的 jar 包里面然后通过 Spring Boot 中约定优于配置的这样一个理念去把这个配置类的全路径放在 classPath:/META-INF/spring,factories 文件里面这样的话Spring Boot 就可以知道第三方 jar 包里面这个配置类的位置这个步骤主要使用到了 Spring 里面的 SpringFactoriesLoader 来完成的③ Spring Boot 拿到所有第三方 jar 包里面声明的配置类以后再通过 Spring 提供的 ImportSelector 这样一个接口来实现对这些配置类的动态加载从而去完成自动装配这样一个动作。 56、Spring Boot 是如何启动 Tomcat 的 首先Spring 在启动时会先创建一个 Spring 容器在创建 Spring 容器过程中会利用 ConditionalOnClass 技术来判断当前 calsspath 中是否存在 Tomcat 依赖如果存在则会生成一个启动 Tomcat 的 BeanSpring 容器创建完之后就会获取启动 Tomcat 的 Bean并创建 Tomcat 对象绑定端口等然后启动 Tomcat 57、SpringBoot 中配置文件的加载顺序是怎样的 优先级从高到低高优先级的配置赋值低优先级的配置所有配置会形成互补配置 命令行参数所有的配置都可以在命令行上进行指定来自 java:comp/env 的 JNDI 属性Java 系统属性System.getProperties()操作系统环境变量jar 包外部的 application-{profile}.properties 或 application.yml(带 spring.profile)配置文件jar 包内部的 application-{profile}.properties 或 application.yml(带 spring.profile)配置文件jar 包外部的 application.properties 或 application.yml(不带 spring.profile)配置文件jar 包内部的 application.properties 或 application.yml(不带 spring.profile)配置文件Configuration 注解类上的 PropertySource 58、什么是 CAP 理论 CAP 理论是分布式领域中非常重要的一个指导理论CConsistency表示强一致性AAvailability表示可用性PPartition Tolerance表示分区容错性CAP 理论指出在目前的硬件条件下一个分布式系统是必须要保证分区容错性而在这个前提下分布式系统要么保证 CP要么保证 AP无法同时保证 CAP。 分区容错性表示一个系统虽然是分布式的但是对外看上去应该是一个整体不能由于分布式系统内部的某个结点挂点或网络出现了故障而导致系统对外出现异常。所以对于分布式系统而言是一定要保证分区容错性的强一致性表示一个分布式系统中各个结点之间能及时的同步数据在数据同步过程中是不能对外提供服务的不然就会造成数据不一致所以强一致性和可用性是不能同时满足的可用性表示一个分布式系统对外要保证可用 59、什么是 BASE 理论 由于不能同时满足 CAP所以出现了 BASE 理论 BABasically Available表示基本可用表示可以允许一定程度的不可用比如由于系统故障请求时间变长或者由于系统故障导致部分非核心功能不可用都是允许的SSoft state表示分布式系统可以处于一种中间状态比如数据正在同步EEventually consistent表示最终一致性不要求分布式系统数据实时达到一致允许在经过一段时间后再达到一致在达到一致过程中系统是可用的 60、什么时 RPC RPC表示远程过程调用对于 Java 这种面向对象语言也可以理解为远程方法调用RPC 调用和 HTTP 调用时有区别的RPC 表示的是一种调用远程方法的方式可以使用 HTTP 协议、或直接基于 TCP 协议来实现 RPC在 Java 中我们可以通过直接使用某个服务接口的代理对象来执行方法而底层则通过构造 HTTP 请求来调用远端的方法所以有一种说法是 RPC 协议 HTTP 协议之上的一种协议也是可以理解的。 61、分布式 ID 是什么有哪些解决方案 在开发中通常会需要一个唯一 ID 来标识数据如果是单体架构我们可以通过数据库的主键或者直接在内存中维护一个自增数字来作为 ID 都是可以的但对于一个分布式系统就会有可能出现 ID 冲突。 解决方案 UUID这种方案复杂度最低但是会影响存储空间和性能利用单机数据库的自增主键作为分布式 ID 的生成器复杂度适中ID 长度较 UUID 更短但是受到单机数据库性能的限制并发量大的时候此方案也不是最优方案利用 redis、zookeeper 的特性来生成 ID比如 redis 的自增命令、zookeeper 的顺序节点这种方案和单机数据相比性能有所提高可以适当选用雪花算法一切问题如果能直接用算法解决那就是最合适的利用雪花算法可以生成分布式 ID底层原理就是通过某台机器在某一毫秒内对某一个数字自增这种方案也能保证分布式架构中的系统 ID 唯一但是只能保证趋势递增。业界存在 tinyid、leaf 等开源中间件实现了雪花算法 62、分布式锁的使用场景是什么有哪些实现方案 在单体架构中多个线程都是属于同一个进程的所以在线程并发执行时遇到资源竞争时可以利用 ReentrantLock、synchronized 等技术作为锁来控制共享资源的使用。 而在分布式架构中多个线程是可能处于不同进程中的而这些线程并发执行遇到资源竞争时利用 ReentrantLock、synchronized 等技术是没办法来控制多个进程中的线程的所以需要分布式锁意思就是需要一个分布式锁生成器分布式系统中的应用程序可以来使用这个生成器所提供的锁从而达到多个进程中的线程使用同一把锁。 目前主流的分布式锁的实现方式有两种 zookeeper利用的是 zookeeper 的临时节点、顺序节点、watch 机制来实现的zookeeper 分布式锁的特点是高一致性因为 zookeeper 保证的是 CP所以由它实现的分布式锁更可靠不会出现混乱redis利用 redis 的 setnx、lua 脚本、消费订阅 等机制来实现的redis 分布式锁的特点是高可用因为 redis 保证的是 AP 所以由它实现的分布式锁可能不可靠不稳定可能会出现多个客户端同时加锁的情况 63、什么是分布式事务有哪些实现方案 在分布式系统中一次业务处理可能需要多个应用来实现比如用户发送一次下单请求就涉及到订单系统创建订单、库存系统减库存而对于一次下单订单创建与库存应该是要同时成功或同时失败的但在分布式系统中如果不做处理就很可能出现订单创建成功但是减库存失败那么解决这类问题就需要用到分布式事务。常用的解决方案有 本地消息表创建订单时减库存消息加入本地事务中一起提交到数据存入本地消息表然后调用库存系统如果调用成功则修改本地消息状态为成功如果调用库存系统失败则由后台定时任务从本地消息表中取出未成功的消息重试调用库存系统消息队列目前 RocketMQ 中支持事务消息它的工作原理是 生产者订单系统先发送一条 half 消息到 Brokerhalf 消息对消费者而言是不可见的再创建订单根据创建订单成功与否向 Broker 发送 commit 或 rollback并且生产者订单系统还可以提供 Broker 回调接口当 Broker 发现一段时间的 half 消息没有收到任何操作命令则会主动调此来查询接口订单是否创建成功一旦 half 消息 commit 了消费者库存系统就会来消费如果消费成功则消息销毁分布式事务成功结束如果消费失败则根据重试策略进行重试最后还失败则进入死信队列等待进一步处理 Seate阿里开源的分布式事务框架支持 AT、TCC 等多种模式底层都是基于两阶段提交理论来实现的 64、雪花算法的实现原理 雪花算法是一种生成分布式全局唯一 ID 的一个算法它会等到一个 64 位长度的 long 类型的数据其中这 64 位数据由四个部分组成 第一个 bit 位是一个符号位因为 id 不会是负数所以它一般为 0接着用 41 个 bit 位来表示毫秒单位的时间戳再用 10 个 bit 位来表示工作机器的 id最后用 12 个 bit 位来表示递增的序列号 然后把这 64 个 bit 位拼接成一个 long 类型的数字这就是雪花算法的一个实现 65、什么是 ZAB 协议 ZAB 协议是 Zookeeper 用来实现一致性的原子广播协议该协议描述了 Zookeeper 是如何实现一致性的分为三个阶段 领导者选举阶段从 Zookeeper 集群中选出一个节点作为 Leader所有的写请求都会由 Leader 节点来处理数据同步阶段集群中所有节点中的数据要和 Leader 节点保持一致如果不一致则要进行同步请求广播阶段当 Leader 节点接收到写请求时会利用两阶段提交来广播该写请求使得写请求像事务一样在其他节点上执行达到节点上的数据实时一致 值得注意的是Zookeeper 只是尽量的达到强一致性实际上仍然只是最终一致性的。 66、为什么 Zookeeper 可以用来作为注册中心 可以利用 Zookeeper 的临时节点和 watch 机制来实现注册中心的自动注册和发现另外 Zookeeper 中的数据都是存在在内存中的并且 Zookeeper 底层采用了 NIO多线程模型所以 Zookeeper 的性能也是比较高的所以可以用来作为注册中心但是如果考虑到注册中心应该是注册可用性的话那么 Zookeeper 则不太合适因为 Zookeeper 是 CP 的它注重的是一致性所以集群数据不一致时集群将不可用所以用 Redis、Eureka、Nacos 来作为注册中心将更合适。 67、Zookeeper 中的领导者选举的流程是怎样的 对于 Zookeeper 集群整个集群需要从集群节点中选出一个节点作为 Leader大体流程如下 集群中各个节点首先都是观望状态一开始都会投票给自己认为自己比较适合作为 Leader然后相互交互投票每个节点会收到其他节点发过来的选票然后 PK先比较 zxidzxid 大者获胜zxid 如果相等则比较 myidmyid 大者获胜一个节点收到其他节点发过来的选票经过 PK 后如果 PK 输了则改票此节点就会投给 zxid 或 myid 更大的节点并将选票放入自己的投票箱中并将新的选票发送给其他节点如果 PK 是平局则将接收到的选票放入自己的投票箱中如果 PK 赢了则忽略所有接收到的选票当然一个节点将一张选票放入到自己的投票箱之后就会从投票箱中统计票数看是否超过一半的节点都和自己所投的节点是一样的如果超过半数那么则认为当前自己所投的节点是 Leader集群中每个节点都会经过同样的流程PK 的规则也是一样的一旦改票就会告诉给其他服务器所有最终各个节点中的投票箱中的选票也将是一样的所以各个节点最终选出来的 Leader 也是一样的这样集群的 Leader 就选举出来了 68、Zookeeper 集群中节点之间数据是如何同步的 首先集群启动时会先进行领导者选举确定哪个节点时 Leader哪些节点是 Follower 和 Observer然后 Leader 会和其他节点进行数据同步采用发送快照和发送 Diff 日志的方式集群在工作过程中所有的写请求都会交给 Leader 节点来进行处理从节点只能处理读请求Leader 节点收到一个写请求时会通过两阶段机制来处理Leader 节点会将该写请求对应的日志发送给其他 Follower 节点并等待 Follower 节点持久化日志成功Follower 节点收到日志后会进行持久化如果持久化成功则发送一个 Ack 给 Leader 节点当 Leader 节点收到半数以上的 Ack 后就会开始提交先更新 Leader 节点本地的内存数据然后发送 commit 命令给 Follower 节点Follower 节点收到 commit 命令后就会更新各自本地内存数据同时 Leader 节点还是将当前写请求直接发送给 Observer 节点Observer 节点收到 Leader 发过来的写请求后直接执行更新本地内存数据最后 Leader 节点返回客户端写请求响应成功通过同步机制和两阶段提交机制来达到集群中节点数据一致 69、Dubbo 支持哪些负载均衡策略 随机从多个服务提供者随机选择一个来处理本次请求调用量越大则分布越均匀并支持按权重设置随机概率轮询一次选择服务提供者来处理请求并支持按权重进行轮询底层采用的时平滑加权轮询算法最小活跃调用数统计服务提供者当前正在处理的请求下次请求过来则交给活跃数最小的服务器来处理一致性哈希相同参数的请求总是发到同一个服务提供者 70、Dubbo 是如何完成服务导出的 首先 Dubbo 会将程序员所使用的 DubboService 注解或 Service 注解进行解析得到程序员所定义的服务参数包括定义的服务名、服务接口、服务超时时间、服务协议等等得到一个 ServiceBean然后调用 ServiceBean 的 export 方法进行服务导出然后将服务信息注册到注册中心如果有多个协议多个注册中心那就将服务按单个协议单个注册中心进行注册将服务信息注册到注册中心后还会绑定一些监听器监听动态配置中心的变更还会根据服务协议启动对应的 Web 服务器或网络框架比如 Tomcat、Netty 等 71、Dubbo 是如何完成服务引入的 当程序员使用 Reference 注解来引入一个服务时Dubbo 会将注解和服务的信息解析出来得到当前所引用的服务名、服务接口是什么然后从注册中心进行查询服务信息得到服务的提供者信息并存在消费端的服务目录中并绑定一些监听来监听动态配置中心的变更然后根据查询得到的服务提供者信息生成一个服务接口的代理对象并放入 Spring 容器中作为 Bean 72、Dubbo 的架构设计是怎样的 Dubbo 中的架构设计是非常优秀的分为了很多层次并且每层都是可以扩展的比如 Proxy 服务代理层支持 JDK 动态代理javassist 等代理机制Registry 注册中心层支持 Zookeeper、Redis 等作为注册中心Protocol 远程调用层支持 Dubbo、HTTP 等调用协议Transport 网络传输层支持 netty、mina 等网络传输框架Serialize 数据序列化层支持 JSON、Hessian 等序列化机制… 73、谈谈你对 Spring Cloud 的理解 Spring Cloud 是 Spring 官方推出来的一套微服务的解决方案准确来说我认为 Spring Cloud 其实是对微服务架构里面出现的各种技术场景定义的一套标准规范然后在这个标准规范里面 Spring 集成了 Netflix 公司里面的 OSS 开源套件比如说① Zuul 去实现应用网关② Eureka 实现服务注册与发现③ Ribbon 实现负载均衡④ Hystrix 实现服务熔断。我们可以去使用 Spring Cloud Netflix 这样一套组件去快速落地微服务架构以及去解决微服务治理的一系列的问题但是随着 Netflix OSS 相关的一些技术组件的闭源和停止维护所有 Spring 官方也自研了一些组件比如说像 Gateway 来实现网关、LoadBalancer 去实现负载均衡另外 Alibaba 里面的开源组件也实现了 Spring Cloud 这样一套标准成为了 Spring Cloud 里面的另外一套微服务解决方案包括 Dubbo 来实现 RPC 通信Nacos 去实现服务注册于发现以及动态配置中心三种去实现服务的限流和服务的降级等等 Spring Cloud 生态出现的出现有两个很重要的意义 在 Spring Cloud 出现之前为了解决微服务架构里面的各种技术问题需要去集成各种开源框架因为标准和兼容性问题所以在实践的时候很麻烦而 Spring Cloud 统一了这样一个标准降低了微服务架构的开发难度只需要在 Spring Boot 的项目基础上通过 starter 启动依赖集成相关组件就能轻松解决各种问题 74、Spring Cloud 有哪些常用组件作用是什么 Eureka注册中心Nacos注册中心、配置中心Consul注册中心、配置中心Spring Cloud Config配置中心Feign / OpenFeignRPC 调用Kong服务网关Zuul服务网关Spring Cloud Gateway服务网关Ribbon负载均衡Spring Cloud Sleuth链路追踪Zipkin链路追踪Seate分布式事务DubboRPC 调用Sentinel服务熔断Hystrix服务熔断 75、Spring Cloud 和 Dubbo 有哪些区别 Spring Cloud 是一个微服务框架提供了微服务领域中很多功能组件Dubbo 一开始是一个 RPC 调用框架核心是解决服务调用间的问题Spring Cloud 是一个大而全的框架Dubbo 则更侧重于服务调用所以 Dubbo 所提供的功能没有 Spring Cloud 全面但是 Dubbo 的服务调用性能比 Spring Cloud 高不过 Spring Cloud 和 Dubbo 并不是对立的是可以结合起来一起使用的。 76、什么是服务雪崩什么是服务限流 当服务A调用服务B服务B调用服务C此时大量请求突然请求服务A假如服务A本身能抗住这些请求但是如果服务C扛不住导致服务C请求堆积从而服务B请求堆积从而服务A不可用这就是服务雪崩解决方式就是服务降级和服务熔断服务限流是指在高并发请求下为了保护系统可以对访问服务的请求进行数量上的限制从而防止系统不被大量请求压垮在秒杀中限流是非常重要的 77、什么是服务熔断什么是服务降级区别是什么 服务熔断是指当服务A调用的某个服务B不可用时上游服务A为了保证自己不受影响从而不再调用服务B直接返回一个结果减轻服务A和服务B的压力直到服务B恢复服务降级是指当发现系统压力过载时可以通过关闭某个服务或限流某个服务来减轻系统压力这就是服务降级 相同点 都是为了防止系统崩溃都让用户体验到某些功能暂时不可用 不同点熔断时下游服务故障触发的降级是为了降低系统负载 78、SOA、分布式、微服务之间有什么关系和区别 分布式架构是指将单体架构中的各个部分拆分然后部署不同的机器或者进程中去SOA 和微服务基本上都是分布式架构SOA 是一种面向服务的架构系统的所有服务都注册在总线上当调用服务时从总线上查找服务信息然后调用微服务是一种更彻底的面向服务的架构将系统中各个功能抽成一个个小的应用程序基本保持一个应用对应的一个服务的架构 79、BIO、NIO、AIO 分别是什么 BIO同步阻塞 IO使用 BIO 读取数据时线程会阻塞住并且需要线程主动去查询是否有数据可读并且需要处理完一个 Socket 之后才能处理下一个 SocketNIO同步非阻塞 IO使用 NIO 读取数据时线程不会阻塞但需要线程主动的去查询是否有 IO 事件AIO异步非阻塞 IO使用 AIO 读取数据时线程不会阻塞并且当有数据可读时会通知给线程不需要线程主动去查询 80、零拷贝是什么 零拷贝指的是应用程序在需要把内核中的一块区域数据转移到另外一块内核区域去时不需要经过先复制到用户空间再转移到目标内核区域去了而直接实现转移。 81、Netty 是什么和 Tomcat 有什么区别特点是什么 Netty 是一个基于 NIO 的异步网络通信框架性能高封装了原生 NIO 编码的复杂度开发者可以直接使用 Netty 来开发高效率的各种网络服务器并且编码简单。 Tomcat 是一个 Web 服务器是一个 Servlet 容器基本上 Tomcat 内部只会运行 Servlet 程序并处理 HTTP 请求而 Netty 封装的是底层 IO 模型关注的是网络数据的传输而不关心具体的协议可定制性更高 Netty 的特点 异步、NIO 的网络通信框架高性能高扩展高定制性易用性 82、Netty 的线程模型是怎么样的 Netty 同时支持 Reactor 单线程模型、Reactor 多线程模型和 Reactor 主从多线程模型用户可根据启动参数配置在这三种模型之间切换。 服务器启动时通常会创建两个 NioEventLoopGroup 实例对应了两个独立的 Reactor 线程池bossGroup 负责处理客户端的连接请求workerGroup 负责处理 I/O 相关的操作执行系统 Task、定时任务 Task 等。用户可根据服务端引导类 ServerBootstrap 配置参数选择 Reactor 线程模型进而最大限度地满足用户的定制化需求。 83、Netty 的高性能体现在哪些方面 NIO 模型用最少的资源做更多的事情内存零拷贝尽量减少不必要的内存拷贝实现了更高效率的传输内存池设计申请的内存可以重用主要指直接内存。内部实现是一颗二叉查找树管理内存分配情况串行化处理读写避免使用锁带来的性能开销。即消息的处理尽可能再同一个线程内完成期间不进行线程切换这样就避免了多线程竞争和同步锁。表面上看串行化设计似乎 CPU 利用率不高并发程度不够。但是通过调整 NIO 线程池的线程参数可以同时启动多个串行化的线程并行运行这种局部无锁化的串行线程设计相比一个队里–多个工作线程模型性能更优。高性能序列化协议支持 protobuf 等高性能序列化协议高效并发编程的体现volatile 的大量、正确的使用CAS 和原子类的广泛使用线程安全容器的使用通过读写锁提升并发性能。 84、Redis 有哪些数据结构分别有哪些典型的应用场景 字符串可以用来做最简单的数据可以缓存某个简单的字符串也可以缓存某个 json 格式的字符串Redis 分布式锁的实现就利用了这种数据结构还包括可以实现计数器、Session 共享、分布式 ID哈希表可以用来存储一些 key-value 对更适合用来存储对象列表Redis 的列表通过命令的组合既可以当做栈也可以当做队列来使用可以用来缓存类似微信公众号、微博等消息流数据集合和列表类似也可以存储多个元素但是不能重复集合可以进行交集、并集、差集操作从而可以实现类似我和某人共同关注的人、朋友圈点赞等功能有序集合集合是有序的有序集合可以设置顺序可以用来实现排行榜功能 85、Redis 分布式锁底层是如何实现的 首先利用 setnx 来保证如果 key 不存在才能获取到锁如果 key 存在则获取不到锁然后还要利用 lua 脚本来保证多个 redis 操作的原子性同时还要考虑锁过期所以需要额外的一个定时任务来监听锁是否需要续约同时还要考虑到 redis 节点挂掉后的情况所以需要采用红锁的方式来同时向 N/2 1 个节点申请锁都申请到了才证明获取锁成功这样就算其中某个 redis 节点挂掉了锁也不能被其他客户端获取到 86、Redis 主从复制的核心原理 Redis 的主从复制是提高 Redis 的可靠性的有效措施主从复制的流程如下 集群启动时主从库间会先建立连接为全量复制做准备主库将所有数据同步给从库。从库收到数据后在本地完成数据加载这个过程依赖于内存快照 RDB在主库将数据同步给从库的过程中主库不会阻塞仍然可以正常接收请求。否则redis 的服务就被中断了。但是这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中为了保证主从库的数据一致性主库会在内存中专门的 replication buffer记录 RDB 文件生成收到的所有写操作最后也就是第三个阶段主库会把第二阶段执行过程中新收到的写命令再发送给从库具体的操作是当主库完成 RDB 文件发送后就会把此时 replocation buffer 中修改操作发送给从库从库再执行这些操作。这样一来主从库就实现同步了后续主库和从库都可以处理客户端读操作写操作只能交给主库处理主库接收到写操作后还会将写操作发送给从库实现增量同步 87、缓存穿透、缓存击穿、缓存雪崩分别是什么 缓存中存放的大多都是热点数据目的就算防止请求可以直接从缓存中获取到数据而不用访问 MySql。 缓存雪崩如果缓存中某一时刻大批热点数据同时过期那么就可能导致大量请求直接访问 MySql 了解决办法就是在过期时间上增加一点随机值另外如果搭建一个高可用的 Redis 集群也是防止缓存雪崩的有效手段缓存击穿和缓存雪崩类似缓存雪崩是大批热点数据失效而缓存击穿是指某一个热点 key 突然失效也导致了大量请求直接访问 MySql 数据库这就是缓存击穿解决方案就算考虑这个热点 key 不设置过期时间缓存穿透假如某一时刻访问 Redis 的大量 key 都在 Redis 中不存在那么也会给数据造成压力这就是缓存穿透解决方案是使用布隆过滤器它的作用就是如果它认为一个 key 不存在那么这个 key 就肯定不存在所以可以在缓存之前加一层布隆过滤器来拦截不存在的 key 88、Redis 和 MySql 如何保证数据一致 方案一先更新 MySql再更新 Redis如果更新 Redis 失败可能仍然不一致方案二先删除 Redis 缓存数据再更新 MySql再次查询的时候再将数据添加到缓存中这种方案能解决方案一的问题但是在高并发下性能较低而且仍然会出现数据不一致的问题比如线程1删除了 Redis 缓存数据正在更新 MySql此时另外一个查询再查询那么就会把 MySql 中老数据又查到 Redis 中方案三延时双删步骤是先删除 Redis 缓存数据再更新 MySql延迟几百毫秒再删除 Redis 缓存数据这样就算在更新 MySql 时有其他线程读了 MySql、把老数据读到了 Redis 中那么也会被删除掉从而把数据保持一致 89、Explain 语句结果中各个字段分别表示什么 列名描述id查询语句中每出现一个 SELECT 关键字MySQL 就会为它分配一个唯一的 id 值某些子查询会被优化为 join 查询那么出现的 id 会一样select_typeSELECT 关键字应对的那个查询的类型table表名partitions匹配的分区信息type针对单表的查询方式全表扫描、索引possible_keys可能用到的索引key实际上使用的索引key_len实际使用到的索引长度ref当使用索引列等值查询时与索引列进行等值匹配的对象信息rows预估的需要读取的记录条数filtered某个表经过搜索条件过滤后剩余记录条数的百分比Extra一些额外的信息比如排序等90、索引覆盖是什么 索引覆盖就是一个 SQL 在执行时可以利用索引来快速查找并且此 SQL 所要查询的字段在当前索引对应的字段中都包含了那么就表示此 SQL 走完索引后不用回表了所需要的字段都在当前索引的叶子节点上存在可以直接作为结果返回了 91、最左前缀原则是什么 当一个 SQL 想要利用索引是就一定要提供该索引所对应的字段中最左边的字段也就是排在最前面的字段比如针对 abc 三个字段建立了一个联合索引那么在写一个 SQL 时就一定要提供 a 字段的条件这样才能用到联合索引这是由于在建立 abc 三个字段的联合索引时底层的 B树 是按照 abc 三个字段从左往右去比较大小进行排序的所以如果想要利用 B树 进行快速查找也得符合这个规则 92、Innodb 是如何实现事务的 Innodb 通过 Buffer PoolLogBufferRedo LogUndo Log 来实现事务以一个 update 语句为例 Innodb 在收到一个 update 语句后会先根据条件找到数据所在的页并将该页缓存在 Buffer Pool 中执行 update 语句修改 Buffer Pool 中的数据也就是内存中的数据针对 update 语句生成一个 RedoLog 对象并存在 LogBuffer 中针对 update 语句生成 undolog 日志用于事务回滚如果事务提交那么则把 RedoLog 对象进行持久化后续还有其他机制将 Buffer Pool 中所修改的数据页持久化到磁盘中如果事务回滚则利用 undolog 日志进行回滚 93、B树和B树的区别为什么 MySql 使用B树 B树的特点 节点排序一个节点可以存多个元素多个元素也排序了 B树的特点 拥有B树的特点叶子节点之间有指针非叶子节点上的元素在叶子节点上都冗余了也就是叶子节点中存储了所有的元素并且排好顺序 MySql 索引使用的是 B树因为索引是用来加快查询的而 B树通过对数据进行排序所以是可以提高查询速度的然后通过一个节点中可以存储多个元素从而可以使得 B树的高度不会太高在 MySql 中一个 Innodb 页就是一个 B树节点一个 Innodb 页默认 16kb所以一般情况下一棵两层的 B树 可以存 2000 万行左右的数据然后通过利用 B树 叶子节点存储了所有数据并进行排序并且叶子节点之间有指针可以很好的支持全表扫描范围查找等 SQL 语句。 94、MySql 锁有哪些如何理解 锁机制数据库为了保证数据的一致性而使用各种共享的资源在被并发访问时变得有序所设计的一种规则。 分类 按操作分类 共享锁也叫读锁。针对同一份数据多个事务读取操作可以同时加锁而不互相影响 但是不能修改数据记录。排他锁也叫写锁。当前的操作没有完成前会阻断其他操作的读取和写入。 按粒度分类 行级锁操作时会锁定当前操作行。开销大加锁慢。会出现死锁。锁定粒度小发生锁冲突概率低并发度高。偏向于 InnoDB 存储引擎表级锁操作时会锁定整个表。开销小加锁快。不会出现死锁。锁定力度大发生锁冲突概率高并发度最低。偏向于 MyISAM 存储引擎页级锁锁的粒度、发生冲突的概率和加锁的开销介于表锁和行锁之间会出现死锁并发性能一般。 按使用方式分类 悲观锁每次查询数据时都认为别人会修改很悲观所以查询时加锁。乐观锁每次查询数据时都认为别人不会修改很乐观但是更新时会判断一下在此期间别人有没有去更新这个数据。 94、MySql 慢查询该如何优化 检查是否走了索引如果没有则优化 SQL 利用索引检查所利用的索引是否是最优索引检查所查字段是否都是必须的是否查询了过多字段查出了多余数据检查表中数据是否过多是否应该进行分库分表了检查数据库实例所在机器的性能配置是否太低是否可以适当增加资源 95、消息队列有哪些作用 解耦使用消息队列来作为两个系统之间的通讯方式两个系统不需要相互依赖异步系统 A 给消费队列发送完消息之后就可以继续做其他事情了流量削峰如果使用消息队列的方式来调用某个系统那么消息将在队列中排队由消费者自己控制消费速度 96、死信队列是什么延时队列是什么 死信队列也是一个消息队列它是用来存放那些没有成功消费的消息的通常可以用来作为消息重试延时队列就是用来存放需要在指定时间被处理的元素的队列通常可以用来处理一些具有过期性操作的业务比如十分钟内未支付则取消订单。 97、Kafka 为什么比 RocketMQ 的吞吐量要高 Kafka 的生成者采用的是异步发送消息机制当发送一条消息时消息并没有发送到 Broker 而是缓存起来然后直接向业务返回成功当缓存的消息达到一定数量时再批量发送给 Broker。这种做法减少了网络 IO从而提高了消息发送的吞吐量但是如果消息生产者宕机会导致消息丢失业务出错所以理论上 Kafka 利用此机制提高了性能却降低了可靠性。 98、Kafka 的 Pull 和 Push 分别有什么优缺点 pull 表示消费者主动拉取可以批量拉取也可以单条拉取所以 pull 可以由消费者自己控制根据自己的消息处理能力来进行控制但是消费者不能及时知道是否有消息可能会拉到消息为空push 表示 Broker 主动给消费者推送消息所以肯定是有消息时才会推送但是消费者不能按自己的能力来消费消息推过来多少消息消费者就得消费多少消息所以可能会造成网络堵塞消费者压力大等问题 99、RocketMQ 的底层实现原理 RocketMQ 由 NameServer 集群、Producer 集群、Consumer 集群、Broker 集群组成消息生产和消费的大致原理如下 Broker 在启动的时候向所有的 NameServer 注册并保持长连接每 30s 发送一次心跳Producer 在发送消息的时候从 NameServer 获取 Broker 服务器地址根据负载均衡算法选择一台服务器来发送消息Conusmer 消费消息的时候同样从 NameServer 获取 Broker 地址然后主动拉取消息来消费 100、消息队列如何保证消息可靠传输 为了保证消息不多也就是消息不能重复也就是生产者不能重复生产消息或者消费者不能重复消费消息首先要确保消息不多发这个不常出现也比较难控制因为如果出现了多发很大的原因是生产者自己的原因如果要避免出现问题就需要在消费端做控制要避免不重复消费最保险的机制是消费者实现幂等性保证就算重复消费也不会有问题通过幂等性也能解决生产者重复发送消息的问题消息不能少意思就是消息不能丢失生产者发送的消息消费者一定要能消费到对于这个问题就要考虑两个方面生产者发送消息时要确认 Broker 确实收到并持久化了这条消息比如 RabbitMQ 的 comfirm 机制Kafka 的 ack 机制都可以保证生产者能正确的将消息发送给 BrokerBroker 要等待消费者真正确认消费到了消息时才删除掉消息这里通常就是消费端 ack 机制消费者接收到一条消息后如果确认没问题了就可以给 Broker 发送一个 ackBroker 接收到 ack 后才会删除 101、TCP 的三次握手和四次挥手 TCP 协议是 7 层网络协议中的传输层协议负责数据的可靠传输在建立 TCP 连接时需要通过三次握手来建立过程是 客户端向服务端发送一个 SYN服务端接收到 SYN 后给客户端发送一个 SYN_ACK客户端接收到 SYN_ACK 后再给服务端发送一个 ACK 再断开 TCP 连接时需要通过四次挥手来断开过程是 客户端向服务端发送 FIN服务端接收 FIN 后向客户端发送 ACK表示我接收到了断开连接的请求客户端你可以不发数据了不过服务端这边可能还有数据正在处理服务端处理完所有数据后向客户端发送 FIN表示服务端限制可以断开连接客户端收到服务端 FIN向服务端发送 ACK表示客户端也会断开连接了
http://www.dnsts.com.cn/news/74920.html

相关文章:

  • 校园网站建设报告浏览器网页版打开网页
  • 桌面软件开发跟网站开发那个免费手机网站模板下载
  • 做网站要用到什么软件做网站需要交接什么
  • 适合网站开发的框架建筑方案设计收费标准
  • 北京地区网站制作公司上海大规模网站建设平台
  • 有什么网站可以在线做试题淘宝作图在哪个网站上做图
  • 做动画片的网站西安网站建设服务商
  • 网站收录提交创网易账号
  • 河南省建设监理协会网站人才十深圳竞价网站
  • 小说网站做编辑wordpress保存远程图片
  • 官方网站建设 省心磐石网络简约网站模版
  • 网站在线优化检测临安市建设局门户网站
  • Dw做网站怎么加logo绿色食品销售网站建设
  • 旅行社网站建设需求分析wordpress 彩色源码
  • 商派商城网站建设方案net域名做企业网站怎么样
  • 婚介 东莞网站建设红色页面网站
  • 网站占有率深圳vi设计公司联系
  • 网站建设需要掌握什么知识求几个夸克没封的a站2023
  • 设置 iis 网站维护中建设银行全球门户网站
  • 国外 家具 网站模板怎么推广自己的物流公司
  • 如何做旅游小视频网站wordpress 多个分类查找
  • 哪个全球购网站做的好处google建站
  • 中国建设银行网站荆门网点查询网站毕业设计任务书
  • 深圳网站设计师云主机与云电脑区别
  • 杭州营销型网站制作山东建设管理局网站
  • 廊坊网站建设费用安卓手机软件开发平台
  • 设置网站标签网页ui素材中心下载
  • 北京网站建设推荐华网天下网站域名收费
  • 东莞网站建设建网站漳州市网站建设费用
  • 临汾工程建设招标投标网站深圳景观设计公司排行