1.分布式事务怎么实现
2PC:投票阶段和提交阶段。一个协调者与多个本地事务执行者。协调者会向多个事务执行者提交执行事务的请求,参与者执行完事务之后会记录自身是执行成功还是失败,然后会把执行结果发给协调者,假如有执行失败的节点就回滚,否则就进行提交。 这篇博客写的不错 分布式事务实现原理
2.spring aop的原理
是通过动态代理实现的,有两种动态代理方式,JDK动态代理(通过反射来实现)以及CGLib动态代理。前者需要被代理的类实现接口 面试官:为什么jdk动态代理只能代理接口实现类? 后者不需要,直接操作字节码来实现。 浅析Spring中AOP的实现原理——动态代理
3.线程池的原理
线程创建以及销毁比较消耗资源,所以通过线程池来统一管理,可以复用线程。线程池是基于生产者消费者模式来实现,有核心线程数,最大线程数,keep alive time最大存活时间(如果线程池的最大线程数超过corePoolSize之后,等待keepAliveTime还存在,那么线程退出)以及等待队列等参数。基本执行流程是,看是否超过核心线程,如果超了排队,如果排队的队列满了,创建线程不超过最大线程,如果超过了最大线程数,拒绝
4.kafka如何保证消息不重复,如何保证消息不丢失,如何保证消息的顺序
消息不丢失: 从3个地方,生产者,broker,以及消费者。 生产者设置ack参数=-1,表示接收者的leader以及follower都接收到消息才认为发送成功。设置消息重试。 broker设置OSR(out-of-sync replica)集合中的副本为leader,设置为false。设置副本数量大于1 Consumer 取消自动提交offset,而是处理完之后手动提交offset Kafka:保证消息不重复不丢失 消息不重复: 消费端保证消息的幂等性
5.spring cloud的熔断组件
Hystrix。可以对服务进行监控,当发现服务出现故障,直接返回失败,而不是让调用方阻塞,同时可以针对不重要服务直接降级返回。比如出现接口长时间超时,线程池资源耗尽
6.spring cloud如何实现服务之间调用的权重比例,eureka服务治理的原理,怎么做负载均衡
- a)eureka可以实现服务注册与发现,以及负载均衡
- b)底层采用了ribbon组件来实现负载均衡,从请求当中根据内置的lb测量选择服务器进行响应。有round robin,权重策略(响应时间越长权重越小)
7.es查询比较快的原理是什么,Lucene的原理,es跟Lucene的关系
- a)倒排索引。建立分词与文档所在位置的映射。
- b)此外es用burst-trie结构来实现term index,这是一种前缀树,通过二分查找就可以快速定位到term。此外一般term index是存储在内存当中的,这样就可以减少磁盘io的次数。 lucene是一个检索工具包。它的基本原理如下: 添加文档的时候先生成正向索引文件,文档生成后再生成倒排索引文件。由于要建立倒排索引,当索引词比较多的时候会占用很多内存空间。从Lucene4之后采用FST,有限状态机转换器,将前缀与后缀分开存储,前缀存内存,后缀词存磁盘。 深度解析 Lucene 轻量级全文索引实现原理 es是基于lucene实现的
8.谈谈orm框架的设计理念
orm框架解决的问题就是在面向对象与关系型数据库中数据类型不匹配的问题,将java对象与数据库表建立映射关系。
9.Redis分布式锁是怎么实现的,Redis有哪些功能
- A)setnx
- B)redis有多种数据类型。可以做为缓存,分布式锁
10.zookeeper的分布式锁怎么实现,zookeeper的原理
zookeeper是一个分布式协调框架
11.jvm怎么监控,怎么查内存溢出或内存泄露
- A)jconsole可以可视化查看内存占用情况 jps可以查看进程信息 jstack查看某个进程的堆栈使用信息
- B)内存泄露是申请的内存没有被及时分配,这是内存泄露,累积的内存泄露可能会导致内存溢出。
- C)内存溢出的原因,生成的对象没有被及时回收,申请的堆内存过小;代码死循环产生了很多对象;从数据库一次性获取的数据量过大 查看内存溢出的原因,看out-of-memory的相关日志以及查看内存占用情况。 最直接的方法是看内存占用情况是从什么时间点开始逐渐上升表现异常,看一下那个时间点做了什么改动。如果无法排查出来再看下虚拟机内存情况。将其中一台异常机器流量通过jmap生成堆转储快照,然后通过jhat查看内存占用情况。
12.string stringbuffer stringbuilder
stringbuffer是线程安全的,很多方法用synchorized来修饰
13.equals和hashcode关系
equals与hashcode都是object对象的方法,用来比较两个对象是否相等。那么为什么有两个方法,因为equals方法比较的比较复杂,而hashcode只是生成一个hash值,比较的效率比较高。那么hashcode效率高为什么还要equals,是因为hashcode并不是完全准确的,因为两个不相同的对象可能生成的hashcode是一样的。因此equals相等,那么hashcode一定相等,但是hashcode相等,两个对象的equals不一定相等。因此比较的时候先比较hashcode,如果不同直接说明两个对象是不同的对象。如果相同则再比较equals方法。而object的hashcode方法是返回当前对象地址的hash值,不满足要求,因此要重写 看似简单的hashCode和equals面试题,竟然有这么多坑!
14.hashmap的底层的实现
数组+链表(链表长度超过8的时候会将链表转化为红黑树)
15.redis和memcahed区别
两者都是基于内存的数据存储系统。
- a)memcached只支持key-value结构,redis支持的数据类型更为丰富。
- b)redis只支持单核,memcached支持多核
- c)而且memcached不支持持久化。
16.redis数据分片
redis分片就是将存储的key分布到不同的服务器上,每个服务器只存储一部分的key。一般来说遇到这样的情况会用hash函数来路由key,但是当扩容实例或者缩减实例的时候会造成无法命中的问题。因此要采取一致性hash算法,但是redis没有一致性hash的概念,因此采用了哈希槽,一个redis集群分成了16384个hash槽,每台redis机器负责一部分hash槽,当新增或者减少机器的时候也会比较方便。
17.http缓存
当web缓存发现请求的资源被缓存的时候会直接返回资源。
18.spring事务实现原理
spring事务是依赖底层的数据库事务来实现的,是使用AOP来实现声明式事务,就是jdk动态代理,以及cglib,前者是面向接口通过反射生成目标代理接口的匿名实现类,cglib通过继承为目标代理类生成代理子类。增强方式一共5种,前置,后置,环绕,异常抛出增强,以及引介增强。声明式事务是通过环绕增强来实现。 【技术干货】Spring事务原理一探
19.设计模式
单例模式,装饰器模式,工厂模式,代理模式,发布订阅模式 如何理解这6种常见设计模式?
20.JVM内存区域划分;CMS垃圾回收过程
- A)程序计数器,虚拟机栈,本地方法栈,堆,方法区
- B)cms回收老年代的对象。初始标记-并发标记-重新标记-并发清除 初始标记是标记那些可以被GC roots可以关联的对象 并发标记是进行gc roots tracing的过程 重新标记是修正因为程序运行导致的标记变动的那一部分,需要停顿
- C)cms的缺点,因为会并发标记,所以会占用一部分CPU资源,这个时候会导致系统整个吞吐量会降低,程序变慢。此外无法处理浮动垃圾,就是在并发清除的过程中产生的垃圾,只能在下一次gc的时候进行收集。此外cms是基于标记清除算法,所以会产生碎片
21.限流算法和实现方式
- A)计数器算法。这种算法有临界问题,比如qps设置为2,这样一种场景,前一秒的2个请求都在后0.5s,下一秒的2个请求都在前0.5s,这样在前一秒与后一秒的1s之间一共4个请求。
- B)漏桶算法。漏桶算法就是类似一个桶,请求会不断地进入到桶里面,如果超过容量就会拒绝,而消费速率是固定的,这样的做法虽然可以保护系统本身,但是问题是短时间内的请求并不能马上能被响应
- C)令牌桶算法。和漏桶算法的思路相同,只不过桶中放的是令牌,每当有请求过来的时候就会取一个令牌立即处理,没有令牌就拒绝。每秒会向桶中放置固定数目的令牌,这就是限速。
22.a,b,c三个线程要依次运行,用线程池如何实现!
让线程按顺序执行8种方法 这个文章用了8种方法。用thread.join方法就可以
23.线程本地变量的作用?原理?项目里面哪里用到过?
ThreadLocal是将变量与一个线程进行绑定,通过threadlocal可以将变量的可见范围维持在一个本线程当中。Threadlocal当中有一个threadlocalMap,用来管理一个线程中有多个threadlocal的情况,key为threadlocal引用,value为threadlocal的值,当发生gc的时候,如果threadlocal没有外部强引用,就会被gc,threadlocalmap的key为null,entry被threadlocalmap对象引用,threadlocalmap又被thread对象引用,如果thread不终结,那么value对象就一直存在不会被回收,就会导致内存泄露,可见泄露的是entry的value对象。防止泄露需要对threadlocal手动remove 一文搞懂 ThreadLocal 原理
24.线程池shutdown做了什么
阻止新来的任务提交,对已经提交的任务不会产生影响,等待执行中的任务执行完毕之后会将闲置线程中断
25.手写代码:写一个简单的hashmap实现
Java实现的一个简单HashMap(翻译) 这个写的不错
26.aop嵌套事物; 事务之六:spring 嵌套事务 这个地方比较复杂 通过实际案例摸清楚Spring事务传播的行为 如果A调用B,A是required,B是required-new,如果B成功,A有异常,那么B成功提交,A回滚,因为是不同事务。 如果A无问题,B有异常,A和B一起回滚,如果A把B的异常捕捉了,B回滚,A提交的时候会有marked as roll-back的exception,也会回滚
27.@Transactional失效的场景
- 1.同一个类,方法A没有@Transactional注解,方法B注解了@Transactional注解,A调用B,事务不生效
- 2.@Transactional注解应用在了private方法上
- 3.A和B都有注解,A内部调用B,B被try/catch住,B产生了异常,回滚失败
28.多线程相关,比如设置多少线程合适
对于CPU密集型的一般设置CPU核心数+1,对于io密集型的,用io占用时间/cpu占用时间+1,因为io密集型的操作当io时间比较长的时候,cpu处于空闲状态,因此这个时候要充分利用cpu,所以比cpu密集型多一些线程。
29.线上问题查看,比如cpu100%怎么查原因
首先使用top命令查看最消耗cpu的进程,然后还是采用top命令查看该进程中最消耗cpu的线程,jstack查看线程具体是在做什么。Cpu100%可能是发生了死循环,产生了很多大的对象,频繁gc,线程出现死锁top命令的.-p用于指定进程,-H用于获取每个线程的信息
30.如何设计一个指定毫秒延迟的队列
- A)可以通过redis的过期key回调的方式来做,redis的key过期的时候会执行一个回调函数,这个过期时间就是设置的延迟时间。
- B)也可以通过zset有序集合这个结构来做,将action发生的时间戳作为score存储,然后开启一个轮询的方法每隔1段时间来查看当前队列中的最小值看看是否要执行了,如果要执行就异步执行
31.Spring事务中,启动了另外一个线程,这个事务怎么算
这样属于不同的事务,因为spring的事务实现是依赖底层数据库的事务来实现的,开启另外一个线程之后是不同的数据库连接,不同的事务。
32.如何把老年代撑爆
生成很大的对象,比如很长的字符串,直接进入老年代。
33.synchronized和可重入锁的区别
- Synchronized 是jvm的关键字,底层是通过monitorenter以及monitorexit字节指令实现的,可重入锁是jdk包中的
- 前者不需要手动释放锁,后者需要
- 前者是非公平锁,后者可以公平也可以非公平(公平锁就是保证多线程下的各线程获取锁的顺序,先到的线程优先获取锁)
- 前者是不可中断的,后者可以中断
34.synchronize原理
每个对象都是一个监视器锁(monitor),线程执行monitorenter指令尝试获取monitor的所有权,如果获取成功那么该线程进入monitor,将进入数设置为1,该线程即为monitor的所有者,如果该线程已经占有,重新进入时只是将monitor的进入数+1,如果其他线程尝试进入,则该线程进入阻塞状态。
35.怎么保持Redis和数据库数据一致
读取操作是没有不一致情况发生的。写入操作要先更新数据库,再删除缓存。总之对于缓存要删除,而不是更新
36.rpc协议,tcp原理
tcp位于传输层,传输控制协议,面向连接,能够提供比较可靠的传输,有拥塞控制以及滑动窗口,滑动窗口发送方以及接收方都有,发送方根据接收方的信息设置自己的窗口大小
37.hashmap和concurrenthashmap
- hashmap在1.8之后数据结构已经是数组+链表+红黑树
- concurrent在1.8之后数据结构已经变成了数组+链表+红黑树,并且是将锁的粒度改成了首节点
38.doubbo接口调用过程
首先服务消费者会通过代理对象Proxy对象发起远程调用,经过网络客户端client发送到提供方的网络层上,将请求进行解码之后,分发器dispatcher将请求分发到线程池当中,通过线程池调用具体的服务
39.hashmap不安全怎么表现的
在jdk1.8之后如果两个线程同时进行put操作,并且恰好两个线程要put的hash值是一样的,那么就有可能出现后面的线程把前面的线程put的值覆盖的情况。Jdk 1.7在put的时候容易出现环形链,因为是采用的头插法的方式
40.Thread.stop/suspend/resume为什么废弃,调用之后监视器的状态是什么
Thread.stop会立即释放锁, 将这个线程监视的所有对象的监视器解锁,可能会导致数据不一致以及文件无法正常关闭以及一些清理性的工作无法正常完成,这种方法造成的ThreadDeath异常不能被捕获。suspend与resume需要搭配使用,可能会造成公共资源的独占,可能会导致死锁
41.Mysql隔离级别,mvcc实现
mvcc的思想是保存数据的历史快照,通过比较版本号来判断数据是否显示。是通过undo log以及read view来实现,undo log保存了事务的历史快照,read view用来判断当前版本的数据是否可见。每个事务开启之前会从数据库获取一个自增长的事务id,这个事务id判断事务执行的先后顺序。当表数据修改之前会把修改前的数据拷贝到undo log当中,如果需要回滚可以通过undo log进行还原。 Read view有几个属性,一是当前系统未提交的版本号的集合trix_ids,最大的版本号以及最小的版本号,以及创建当前read view的事务版本号。如果数据的事务id小于最小版本号,说明该数据之前已经提交了,可以显示,如果数据的事务id大于最大版本号,说明数据是在当前read view创建之后才产生,不显示数据。 如果数据的版本号介于最大和最小版本号之间,分几种情况,如果在trix_ids当中找不到,说明已经提交了,可以显示。如果数据版本号就等于创建当前read view的版本号,自己的事务可以看到自己创建的数据,可以看到。如果数据的版本号存在于trix_ids集合中,并且不等于创建当前read view的版本号,说明是其他事务还未提交的事务,不能显示。 如果read view无法满足提交,从undo log当中获取数据的历史版本,然后再和read view进行匹配,直到找到满足条件的数据,或者找不到返回空。 RC隔离级别事务当中每次查询都会获取一个新的read view,而rr级别的事务中每次获取的都是同一个read view,所以可以重复读 数据库基础(四)Innodb MVCC实现原理 这篇文章写的很不错 DB_TRX_ID:记录操作该数据库事务的事务id DB_ROLL_PTR:指向上一个版本在undo log中的位置指针
42.Mysql undolog文件格式
聚簇索引除了记录数据的基本信息,还会自动添加trx_id以及roll_pointer隐藏列,如果没有定义主键或者unique键,还会新增row_id隐藏列 Redo log是在事务执行过程中写入,是为了保证事务的一致性,防止系统崩溃出现数据丢失。 Undo log是在事务执行之前写入,记录数据修改前的值,保证事务的原子性
43.Mysql 一致性锁定读和非锁定读的区别
非锁定读指的是读取的时候不加锁,其他事务可以修改记录,就是快照读,是用mvcc来实现的。在READ COMMITTED事务隔离级别下,一致性非锁定读总是读取被锁定行的最新一份快照数据。而在REPEATABLE READ事务隔离级别下,则读取事务开始时的行数据版本。 锁定读意味着读取的时候会加锁,select 。。。 for update加的是X锁,阻塞其他事务的修改以及读取请求。Select 。。。 in share mode加的是S锁,阻塞修改,但是不阻塞读取。
44.Redis aof rdb复制的区别
- a)rdb是将数据定期同步到磁盘当中。redis会fork一个子进程来完成这件事情,主进程不会进程io操作
- b)aof是将执行的写命令记录下来
45.Redis 复制实现
- A)在redis版本2.8之前只是通过全量复制+命令传播来实现的的,在之后可以实现部分复制的功能 全量复制就是生成RDB快照文件,然后通过网络传到slave节点当中,slave通过这个快照文件来进行恢复。之后继续发送缓冲区内新增的命令,实现数据的一致
- B) 之后的版本可以通过全量复制+部分同步+命令传播来实现
46.Thread blocked、waiting、time-waiting 状态的区别?
blocked状态是线程无法进入同步代码块造成的,在等待monitor锁。 waiting是一个线程在等待另外一个线程执行一个动作时处于这种状态,当前线程调用object.wait或者Thread.join(),或者locksupport.park()方法,进入等待
47.服务提供方修改了一个接口方法,对这个方法添加了一个参数,但是没有通知调用方,例如 :void say(long id);==>void say(long id ,int type);框架应该怎么支持这种升级
- a)参数尽量写成包装类,可以放心新增参数
- b)provider暴露服务的时候提供多个版本,可以通过指定版本号调用不同的接口
48.Rpc框架中,VI的API提供的美剧enumt{a,b,c};v2的API 提供的enumt{a,b,d};反序列化失败怎么处理?
Enum反序列化问题 避免用枚举类型作为反序列化,但是如果一旦出现了这样的问题,可以用class代替Enum类型,valueOf方法用map.get方法代替。enum在序列化的时候新加枚举失败的原因是enum序列化只是将枚举的名称序列化,反序列化是通过valueOf通过名称寻找枚举对象
49.JVM优雅关机
jvm有shutdown hooks机制,在关闭之前可以通过调用shutdown hooks来进行收尾工作。这个shutdown hooks本质上是一个线程,但是并不会保证调用的顺序性问题,因此可能会造成并发或者同步的问题,建议在一个hook中执行一系列操作。jvm安全退出有一定的时间,如果超时的话系统会直接强制关闭
50.redis单线程怎么考虑并发
- A)redis主要是基于内存的操作
- B)采用了多路复用IO
- C)Redis单线程节省了线程切换的开销
51.redis有序集合是怎么实现的
有序集合底层可以采用压缩列表以及跳跃链表两种编码方式方式来实现,压缩列表是将元素的值与对应的分值放在相邻的节点,内部是按照分值保持有序状态。而后者跳跃链表是维护了一个跳跃链表以及一个字典,跳跃链表的每个节点都维护了元素的值以及对应的得分,而字典的键存储的是节点的值,value存储的是节点的得分。之所以采用这两种数据结构是因为跳跃链表比较方便做排序以及范围查询,而字典比较方便通过元素寻找到对应的元素的得分
52.mybatis一级缓存,二级缓存作用级别,如何置失效
mybatis有一级缓存以及二级缓存,一级缓存是sqlsession级别的缓存,在相同的sqlsession,以及相同的sql语句的情况下,第一次会将数据库的数据缓存到mybatis缓存中,第二次会直接从mybatis缓存获取数据。 二级缓存是mapper级别的缓存,多个sqlsession操作同一个mapper的sql语句,多个sqlsession操作数据库得到的数据会存储在二级缓存当中,多个sqlsession可以共用二级缓存
53.线程安全的三个要素,如何实现的
- 1>原子性 是通过本地方法CAS来实现,一旦使用CAS就有可能出现ABA问题
- 2>可见性 java通过volatile来实现可见性
- 3>有序性 通过volatile产生内存屏障,避免指令重排序。有load barrier以及store barrier两种,前者是要求在store barrier之前的指令都在store barrier之前执行。Load barrier是需要在load barrier之后的指令都要在load barrier之后执行 内存屏障参见:内存屏障
线程安全——概念及三要素(一) 这个说的还可以
54.redis分布式锁如何实现,一定是线程安全的吗?
如果只是使用redis的命令,客户端1先加锁,然后去操作,但是操作时间大于锁的过期时间,然后锁发生超时释放锁,这个时候客户端2加锁,此时客户端1操作完了,然后释放锁,释放的其实是客户端2的锁。 为了解决释放别人的锁的问题,每个客户端加锁的时候可以设置一个uuid,释放的时候只有当设置的uuid是自己设置的再释放。这样就会出现两步,第一步比较是否一致,第二步释放,此时由于没有原子性操作,因此就可能出现问题,因此这个时候可以用lua脚本来实现,保证原子性。 上面说的是防止释放别人的锁的问题。那么如何防止提前过期呢,一种方式是尽量延长时间,但是这样只能缓解而不能避免问题,那么可以采用一种方法,就是自动续期,当执行操作的时间还没结束但是锁快过期的时候自动续期,java有一个封装好的叫redission。 此外因为redis是单点的,如果这个节点宕机那么同样会产生问题,因此产生了redlock的概念,也就是红锁,就是部署多个redis主实例,每次加锁向这多个实例都加锁,只有当超过一半加锁成功,并且发起请求时间与加锁之后的时间的差值小于过期时间才认为加锁成功(之所以这样做是因为多个节点网络更为复杂,可能会出现超时)。然后去操作资源,然后通过lua脚本释放锁 Redis分布式锁到底安全吗?
55.并发情况下我们怎么实现库存不超发
- A)使用队列使请求串行化
- B)使用redis的分布式锁。在查询库存以及扣减库存的时候利用分布式锁保证只有一个线程执行,这样性能比较差
- C) 使用数据库的悲观锁
56.dubbo和springCloud的区别
dubbo底层基于netty的框架,采用的tcp协议,采用hessian进行序列化和反序列化 springCloud是基于http协议+rest接口,协议报文更大,但是更为灵活。 注册中心:dubbo是基于zookeeper,而springCloud是eureka,也可以是zookeeper, 如果mq挂了,可以用数据库要发送的消息先存储起来
57.netty线程模型,bio和nio优缺点
java的几种网络io模型和一般意义上的io模型不太一样,一般意义上的io模型有五种,java有bio,nio,aio三种 bio就是同步阻塞io,一个请求来了就创建一个连接,也就是请求与响应的一对一,如果短时间内有大量的请求进来就会造成线程资源耗尽。适用于连接比较少并且固定的架构 NIO是基于多路复用技术的io模型。适用于连接比较多但是连接时长比较短的架构,比如聊天服务器。Java nio主要的概念是缓冲区,通道,以及选择器。NIO是面向缓冲区编程 AIO非阻塞异步io模型。适用于连接比较多并且连接时长比较长的架构。 BIO是以流的形式处理数据,NIO是以块的形式处理数据,块的IO效率比流要高很多。BIO基于字节以及字符流操作,NIO基于通道和缓冲区操作 这篇文章写的比较好 理解什么是BIO/NIO/AIO
58.缓存挂了如何避免大量并发请求访问到数据库
- A)对接口访问进行限流。如果超过限制直接拒绝
- B)将请求发送到消息队列进行排队
59.同步消息与异步消息
同步消息和异步消息的区别是同步消息发送方发送完消息之后会阻塞自己进程,直到收到接收方的响应。 异步消息是发送方发送消息不用等待接收方进行确认。
60.怎么自动降级
- A)发现访问的数据库或者http服务响应比较慢或者超时后,如果不是核心的服务会自动降级
- B)统计失败的次数进行自动降级
61.如何防DDos
ddos是分布式拒绝服务(distributed denial of service),就是利用多台计算机作为攻击平台。 防ddos可以隐藏服务器真实ip,网站请求ip过滤 如果真的遇到了可以临时性带宽扩容 DDOS 攻击的防范教程
62.线程池的种类以及如何创建
63.web过滤器和拦截器的区别
过滤器filter是javaee自带的功能,而拦截器interceptor是springmvc的功能。 过滤器直接实现filter接口,基于函数回调实现,拦截器是通过反射机制来实现的 过滤器的初始化是在进入web容器之后,在进入servlet容器之前实现,而拦截器是在进入servlet容器之后,在进入controller之前初始化 filter接口中定义了三个方法,init(),doFilter(),destroy()方法,拦截器HandlerInterceptor方法中定义了preHandle(),postHandle()以及afterCompletion()方法 过滤器 和 拦截器 6个区别 Tomcat容器,Servlet容器,Spring容器的包含关系 这个是web容器与sevlet容器的示意图
64.String为什么不可变?可不可以通过反射修改?final修饰的对象必须需要一开始初始化好?还是可以在构造方法里边初始化?
因为String是final不可变的,String对象缓存在缓存池当做,被多个客户共享,如果可变就可能会出现一个用户对一个字符串的修改影响到另外一个用户。 可以通过反射修改
65.线程池结束两个方法、有什么区别?
shutdown以及shutdownnow。前者调用之后不能提交新的任务,已提交的任务会继续执行。后者会停止当前正在执行的task
66.TreeMap的结构、以及TreeMap的排序怎么实现的?
treemap的底层是通过红黑树来实现
67.dubbo的架构图?原理?SPI机制和JDK默认的有什么区别?
SPI全称Service Provider Interface,是一种服务发现机制。本质上是将接口实现类的全限定名配置在文件当中,通过服务器加载读取配置文件,加载实现类。可以在运行的时候动态加载实现类。dubbo的spi机制并没有用java自身的spi,与jdk自身的spi不同的是,dubbo的spi是通过键值对的方式来进行配置,此外要在接口上标注@SPI注解 JDK/Dubbo/Spring 三种 SPI 机制,谁更好?