1.Spring,SpringMVC,SpringBoot,SpringCloud有什么区别和联系?
- Spring是核心,java开发框架,提供了基础功能
- SpringMVC是基于Spring的一个MVC框架,web框架
- Spring Boot 是为简化Spring配置的快速开发整合包
- Spring Cloud是构建在Spring Boot之上的服务治理框架
2.你能说说Spring框架中Bean的生命周期吗?
- 实例化Bean
- 将Bean纳入Spring容器管理
- 一系列接口实现判断
- 后置处理器阶段
- 销毁阶段
3.如何决定使用 HashMap 还是 TreeMap?
TreeMap继承自SortedMap接口
TreeMap
的Key值是要求实现 java.lang.Comparable,所以迭代的时候TreeMap默认是按照Key值升序排序的;TreeMap的实现是基于红黑树结构。适用于按自然顺序或自定义顺序遍历键(key)。
HashMap继承AbstractMap抽象类
HashMap
的Key值实现散列 hashCode(),分布是散列的、均匀的,不支持排序;数据结构主要是桶(数组),链表或红黑树。适用于在Map中插入、删除和定位元素。
4.分库分表之后,id 主键如何处理?
分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大
- 基于数据库的实现方案
- 数据库自增 id(并发不高)
- 设置数据库 sequence 或者表自增字段步长(服务节点固定,步长也固定,不利于扩展)
- UUID(如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的)
- 获取系统当前时间(将当前时间跟很多其他的业务字段拼接起来,作为一个 id)
- snowflake 算法
0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000
// 不用的1个bit 41bit时间戳 5bit机房id,5bit机器id 12bit序列号
public class IdWorker {
private long workerId;
private long datacenterId;
private long sequence;
public IdWorker(long workerId, long datacenterId, long sequence) {
// sanity check for workerId
// 这儿不就检查了一下,要求就是你传递进来的机房id和机器id不能超过32,不能小于0
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(
String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(
String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
System.out.printf(
"worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = sequence;
}
private long twepoch = 1288834974657L;
private long workerIdBits = 5L;
private long datacenterIdBits = 5L;
// 这个是二进制运算,就是 5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
private long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 这个是一个意思,就是 5 bit最多只能有31个数字,机房id最多只能是32以内
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
private long sequenceBits = 12L;
private long workerIdShift = sequenceBits;
private long datacenterIdShift = sequenceBits + workerIdBits;
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private long sequenceMask = -1L ^ (-1L << sequenceBits);
private long lastTimestamp = -1L;
public long getWorkerId() {
return workerId;
}
public long getDatacenterId() {
return datacenterId;
}
public long getTimestamp() {
return System.currentTimeMillis();
}
public synchronized long nextId() {
// 这儿就是获取当前时间戳,单位是毫秒
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
System.err.printf("clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 这个意思是说一个毫秒内最多只能有4096个数字
// 无论你传递多少进来,这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0;
}
// 这儿记录一下最近一次生成id的时间戳,单位是毫秒
lastTimestamp = timestamp;
// 这儿就是将时间戳左移,放到 41 bit那儿;
// 将机房 id左移放到 5 bit那儿;
// 将机器id左移放到5 bit那儿;将序号放最后12 bit;
// 最后拼接起来成一个 64 bit的二进制数字,转换成 10 进制就是个 long 型
return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
IdWorker worker = new IdWorker(1, 1, 1);
for (int i = 0; i < 30; i++) {
System.out.println(worker.nextId());
}
}
}
5.消息队列中,如何保证消息的顺序性?
RabbitMQ:拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理
Kafka:
1)一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个
2)写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性
6.单例模式有几种写法?
适用场景
- 需要频繁实例化然后销毁的对象
- 创建对象耗时过多或耗资源过多,但又经常用到的对象
- 有状态的工具类对象
- 频繁访问数据库或文件的对象
懒汉模式
1.普通懒汉
// 优点:启动速度快,懒加载,节约资源
// 缺点:写起来麻烦,线程不安全(不适用于多线程)
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
2.普通懒汉synchronized
// 优点:写起来简单,且绝对线程安全
// 缺点:并发性能极差,事实上完全退化到了串行
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
3.双检锁1.0
// 似乎已经达到了理想的效果:懒加载+线程安全。
// 但是DCL仍然是线程不安全的,由于指令重排序,你可能会得到“半个对象”,即”部分初始化“问题
public class Singleton {
private static Singleton singleton = null;
public int f1 = 1; // 触发部分初始化问题
public int f2 = 2;
private Singleton() {}
public static Singleton getInstance() {
// may get half object
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
4.双检锁2.0
// 适用于性能敏感的场景。但后面我们将了解到,就算是线程安全的,还有一些办法能破坏单例
public class Singleton {
private static volatile Singleton singleton = null;
public int f1 = 1; // 触发部分初始化问题
public int f2 = 2;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
// must be a complete instance
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
饿汉模式
// 优点:天生的线程安全(得益于类加载机制)
// 缺点:有可能造成资源浪费
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
静态内部类(饿汉的变种)
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
private SingletonHolder() {}
}
}
枚举模式
基础枚举
// ThreadSafe
public enum Singleton {
SINGLETON;
}
7.Redis中是如何实现分布式锁的?
分布式锁常见的实现方式
- 数据库乐观锁
- 基于ZooKeeper的分布式锁
- 基于Redis的分布式锁
Redis实现分布式锁的条件:
互斥性:在任意时刻,只有一个客户端能持有锁
不能死锁:客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁
容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁
Redis实现代码
// 获取锁(unique_value可以是UUID等)
SET key unique_value NX PX 30000
// 释放锁(lua脚本中,一定要比较value,防止误解锁)
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
redlock算法
Redisson实现(redlock算法)
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock)、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。
Redisson 分布式重入锁用法
Redisson 支持单点模式、主从模式、哨兵模式、集群模式,这里以单点模式为例:
// 1.构造redisson实现分布式锁必要的Config
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:5379").setPassword("123456").setDatabase(0);
// 2.构造RedissonClient
RedissonClient redissonClient = Redisson.create(config);
// 3.获取锁对象实例(无法保证是按线程的顺序获取到)
RLock rLock = redissonClient.getLock(lockKey);
try {
/**
* 4.尝试获取锁
* waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
* leaseTime 锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
*/
boolean res = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);
if (res) {
//成功获得锁,在这里处理业务
}
} catch (Exception e) {
throw new RuntimeException("aquire lock fail");
}finally{
//无论如何, 最后都要解锁
rLock.unlock();
}
RedissonLock是可重入的,并且考虑了失败重试,可以设置锁的最大等待时间, 在实现上也做了一些优化,减少了无效的锁申请,提升了资源的利用率。
需要特别注意的是,RedissonLock 同样没有解决 节点挂掉的时候,存在丢失锁的风险的问题。而现实情况是有一些场景无法容忍的,所以 Redisson 提供了实现了redlock算法的 RedissonRedLock,RedissonRedLock 真正解决了单点失败的问题,代价是需要额外的为 RedissonRedLock 搭建Redis环境。
所以,如果业务场景可以容忍这种小概率的错误,则推荐使用 RedissonLock, 如果无法容忍,则推荐使用 RedissonRedLock。
8.说说Object类下面有几种方法呢?
1.Object()
2.registerNatives()
3.clone():clone()函数的用途是用来另存一个当前存在的对象
4.getClass():该方法返回的是此Object对象的类对象/运行时类对象Class
5.equals()
6.hashCode():该方法用来返回其所在对象的物理地址(哈希码值)
7.toString()
线程通信
8.wait()
9.wait(long timeout)
10.wait(long timeout, int nanos)
11.notify() 12. notifyAll()
13.finalize():垃圾回收相关
9.说说hashCode() 和 equals() 之间的关系?
1.不会创建类对应的散列表:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合
在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!
2.会创建类对应的散列表:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合
在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
1)如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。
2)如果两个对象hashCode()相等,它们并不一定相等
10.Redis 面试常见问答
- 缓存雪崩(缓存同时大量失效)
解决方案:
对缓存做高可用,防止缓存宕机
断路器限流
- 缓存穿透
- 缓存并发竞争(多个客户端写一个 key,如果顺序错了,数据就不对了)
解决方案:使用分布式锁
- 缓存和数据库双写不一致
1.先更新数据库,再更新缓存(并发写的时候会有问题)
2.先删缓存,再更新数据库
3.先更新数据库,再删除缓存
11.分布式系统接口,如何避免表单的重复提交?
幂等性
- 效果:系统对某接口的多次请求,都应该返回同样的结果!(网络访问失败的场景除外)
- 目的:避免因为各种原因,重复请求导致的业务重复处理
幂等性的实现方式(针对新增、修改)
实现方法:客户端做某一请求的时候带上识别参数标识AddId,服务端对此标识进行识别,重复请求则重复返回第一次的结果即可;
这个参数标识什么时候更新呢?只有在保存成功并且清空表单之后,才变更这个参数标识,从而实现新数据的表单提交。
12.说说项目中单点登录的实现原理?
- 共享Session(应用体系简单,子系统很少的情况)
- 基于OpenId的单点登录(用于C/S与B/S相结合的系统)
- 基于Cookie的OpenId存储方案
- B/S多域名环境下的单点登录处理
13.说说 Redis 的过期策略
Redis采用的是 定期删除 + 懒惰删除策略
定期删除策略
Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,默认每 100ms 进行一次过期扫描:
1.随机抽取 20 个 key
2.删除这 20 个key中过期的key
3.如果过期的 key 比例超过 1/4,就重复步骤 1,继续删除
懒惰删除策略
Redis 为什么要懒惰删除(lazy free——redis4.0):当要删除的对象太大会造成单线程卡顿。
unlink 指令:它能对删除操作进行懒处理,丢给后台线程来异步回收内存
flushdb/flushall async:后台慢慢清空数据库
内存淘汰机制(配置 maxmemory-policy):
- noeviction:当内存超出 maxmemory,写入请求会报错,但是删除和读请求可以继续
- allkeys-lru:当内存超出 maxmemory,在所有的 key 中,移除最少使用的key。只把 Redis 既当缓存是使用这种策略。(推荐)
- allkeys-random:当内存超出 maxmemory,在所有的 key 中,随机移除某个 key
- volatile-lru:当内存超出 maxmemory,在设置了过期时间 key 的字典中,移除最少使用的 key。把 Redis 既当缓存,又做持久化的时候使用这种策略
- volatile-random:当内存超出 maxmemory,在设置了过期时间 key 的字典中,随机移除某个key
- volatile-ttl:当内存超出 maxmemory,在设置了过期时间 key 的字典中,优先移除 ttl 小的
LRU 算法(Least Recently Used)最近最久未被使用:淘汰最长时间没有被使用的
LFU算法(Least Frequently Used)最不经常使用:淘汰一段时间内,使用次数最少的页面
14.进程与线程的区别
15.常见的多线程面试题
创建线程有几种不同的方式:
- 继承Thread类
- 实现Runnable接口
- 应用程序可以使用Executor框架来创建线程池
线程的状态:
新建
就绪(可运行)
运行
阻塞:等待阻塞、同步阻塞、其他阻塞
死亡
同步方法与同步代码块的区别:
- 同步方法默认使用this或者当前类的class对象作为锁
- 同步代码块可以选择以什么来加锁,比同步方法更细颗粒度,可以选择只同步会发生问题的部分而不是整个方法
多线程产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 保持和请求条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
- 不可剥夺性:进程已获得资源,在未使用完成前,不能被剥夺。
- 循环等待条件(闭环):若干进程之间形成一种头尾相接的循环等待资源关系。
16.HashMap详述
// 线程不安全,允许null键和null值,其余 HashMap 与 Hashtable 大致相同
Map map = Collections.synchronizedMap(new HashMap());
HashMap的底层主要是基于 数组和链表 来实现的
HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表
HashMap的性能参数
1.HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。
2.HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。
3.HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。
17.ArrayList与LinkedList
ArrayList的底层是基于 数组,LinkedList的底层是基于 双向链表
容量参数是ArrayList和Vector等基于数组的List的特有性能参数
总结
1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象
2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的
3.LinkedList不支持高效的随机元素访问
4.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
18.Java序列化与反序列化
- Java序列化:将Java对象转换为字节序列的过程;核心作用是对象状态的保存与重建
- Java反序列化:将字节序列恢复为Java对象的过程;根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象
为什么需要序列化与反序列化?
- 对象序列化可以实现分布式对象
- java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据
- 序列化可以将内存中的类写入文件或数据库中
- 对象、文件、数据,有许多不同的格式,很难统一传输和保存
19.Java线程(JVM线程)与操作系统线程
Java 线程状态的改变通常只与自身显式引入的机制有关
操作系统的线程状态是围绕着 cpu 这一核心去述说的
20.HashMap不安全的体现
1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失(采用的是头插法)
2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况(采用的是尾插法)
1.7中采用数组+链表,1.8采用的是数组+链表/红黑树(在1.7中链表长度超过一定长度后就改成红黑树存储)
1.7扩容时需要重新计算哈希值和索引位置,1.8并不重新计算哈希值,巧妙地采用和扩容后容量进行&操作来计算新的索引位置
21.消息队列介绍
1.消息队列特性
- 业务无关
- FIFO
- 容灾
- 性能
2.消息队列优点
- 提高系统响应速度
- 提高系统稳定性
- 异步
- 解耦
- 削峰
3.分布式产生的问题
- 并发问题
- 容错
- 可横向扩展
- 简单的、统一的操作机制
4.常见消息队列对比
22.Linux环境下的Network IO
IO时涉及两个系统对象:调用Io的进程(线程)、系统内核。
涉及两个阶段:数据准备阶段、将数据从内核拷贝到进程中
1.blocking IO —— 两个阶段都阻塞
2.nonblocking IO —— 2阶段阻塞
3.IO multiplexing —— 两个阶段都阻塞
4.signal driven IO
5.asynchronous IO
A,B,C,D四个人钓鱼
A用的是最老式的鱼竿,所以呢,得一直守着,等到鱼上钩了再拉杆
B的鱼竿有个功能,能够显示是否有鱼上钩,所以呢,B就和旁边的MM聊天,隔会再看看有没有鱼上钩,有的话就迅速拉杆
C用的鱼竿和B差不多,但他想了一个好办法,就是同时放好几根鱼竿,然后守在旁边,一旦有显示说鱼上钩了,它就将对应的鱼竿拉起来
D是个有钱人,干脆雇了一个人帮他钓鱼,一旦那个人把鱼钓上来了,就给D发个短信
23.MySQL
MySQL查询字段区不区分大小写?—— 不区分
如何区分?
方案一:MySQL默认的字符检索策略:utf8_general_ci,不区分大小写;utf8_bin,区分大小写。
-- 创建表
CREATE TABLE testt(
id INT PRIMARY KEY,
name VARCHAR(32) NOT NULL
) ENGINE = INNODB COLLATE =utf8_bin;
-- 修改表结构的Collation属性
ALTER TABLE TABLENAME MODIFY COLUMN COLUMNNAME VARCHAR(50) BINARY CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL;
方案二:直接修改sql语句,在要查询的字段前面加上binary关键字
-- 在每一个条件前加上binary关键字
select * from user where binary username = 'admin' and binary password = 'admin';
-- 将参数以binary('')包围
select * from user where username like binary('admin') and password like binary('admin');
MySQL innodb有多少种日志:
- 错误日志
- 查询日志
- 慢查询日志
- 二进制日志:记录对数据库执行更改的所有操作
- 中继日志:中继日志也是二进制日志,用来给slave 库恢复
- 事务日志
MySQL innodb的事务与日志的实现方式
事务日志是通过redo和innodb的存储引擎日志缓冲(Innodb log buffer)来实现的
1.开启事务时,会记录该事务的lsn(log sequence number)号
2.事务执行时,会往InnoDB存储引擎的日志的日志缓存里面插入事务日志
3.事务提交时,必须将存储引擎的日志缓冲写入磁盘
MySQL binlog的几种日志录入格式以及区别
1.Statement:每一条会修改数据的sql都会记录在binlog中
2.Row:不记录sql语句上下文相关信息,仅保存哪条记录被修改
3.Mixedlevel: 以上两种level的混合使用
24.JVM
判断对象是否存活
- 引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器值减1;任何时刻计数器为0的对象就是不能再被引用的
- 可达性分析法
通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则证明此对象是不可用的
25.Dubbo
1.Dubbo是什么?
Dubbo简单来说,就是个服务框架,远程服务调用的分布式框架。
其核心包含:
1.远程通讯:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
2.集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
3.自动发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
2.Dubbo能做什么?
- 透明化的远程方法调用
- 负载均衡及容错机制
- 服务自动注册与发现
3.Dubbo内置了如下容器:Spring Container、Jetty Container、Log4j Container
4.Dubbo的核心配置
5.Dubbo有哪几种集群容错方案?
6.Dubbo有哪几种负载均衡策略?
7.Dubbo默认采用的是 Netty 通信框架
8.Dubbo VS SpringCloud
没有好坏,只有合不合适。
SpringCloud优势
约定优于配置
开箱即用、快速启动
适用于各种环境
轻量级的组件
组件支持丰富,功能齐全
二者比较
- 传输协议
dubbo由于是二进制的传输,占用带宽会更少
springCloud是http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大
- 开发难度
dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决
- 接口协议
springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级
- 注册中心
dubbo的注册中心可以选择zk,redis等多种,springcloud的注册中心只能用eureka或者自研
26.ZooKeeper
27.Java集合框架
1.HashMap和HashTable的区别?
- HashMap线程不安全,HashTable线程安全
- HashMap 允许 null key 和 null value,而 HashTable 不允许
2.ArrayList 和 LinkedList 的区别是什么?
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
3.ArrayList 和 Vector 的区别是什么?
- ArrayList线程不安全,Vector线程安全
- Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法
4.Array 和 ArrayList 有何区别?
- 支持的对象类型
- 数组长度可变性
5.HashSet的实现原理
HashSet的底层是基于HashMap(的Key)