初识集合
- List接口:实现类有ArrayList和LinkedList
- ArrayList:底层是数组,大小是可变的;初始大小为10,增量是1.5(在每次调用add()方法时都会先去计算数组空间是否够用,如果够,直接添加,不够则扩容,使用grow(),当第11个元素进来时数组空间已满,会扩充到15,扩容完成之后调用arraycopy()方法对数组进行拷贝)
- LinkedList:底层是链表。链表增删快,查询慢
- Vector 底层结构是数组,线程安全的,增量是2
为什么日常开发中用的最多的是ArrayList 由底层的数据结构决定,正在日常开发中,查询比增删多,即便是增删也是往往在List的尾部添加即可,复杂度O(1)比较低,对于增删来说,底层调用的copyOf()方法优化过,因此也不会太慢
集合间的继承关系
线程安全的list还有什么?
- 用Collection来将ArrayList包装一下,变成线程安全。 Collections.synchronizedList(new ArrayList<>());
- java.util.concurrent包下还有一个类,叫做CopyOnWriteArrayList :底层是通过复制数组来实现的
add()方法实现的时候会加lock锁,复制一个新的数组,往新的数组里添加真正的元素,最后在把数组的指向变更为新的数组
get()和size()方法只是获取数组指向的 数组的元素或者大小,读不加锁,写加锁
其实跟文件系统的cow机制很像,就是说在修改数据的时候,不会直接在原来的数据位置进行操作,而是重新找个位置修改
也就是说:要修改数据块A的内容,先把A读出来,写到B块里面去。如果这时候断电了,原来A的内容还在。
这样做的好处就是可以保证数据的完整性,瞬间挂掉了容易恢复
缺点:
比较耗费内存(每次set()/add()都会复制出一个数组)
只能保证数据的最终一致性,不能保证数据的实时一致性(A、B两个线程,A线程读数据还没读完,B线程清空了,此时A线程还是可以读出来的)
Map
- 在hashMap中,是怎么判断一个元素是否相同?
首先比较hash值,然后用==和equals判断元素是否相同。如果只有hash值相同,说明该元素哈希冲突了,如果三者比较完全一样,说明该元素是同一个 - LinkedHashMap底层结构【数组+链表+双向链表】,保证了插入是有序的,在遍历的时候用的是双向链表遍历,不会影响遍历的性能
- TreeMap的底层数据结构【红黑树】,key不能为null,有序(通过Comparator比较,如果comparator为null,使用自然顺序)
- 线程安全的map?
- hashMap不是线程安全的,线程安全的可以使用ConcurrentHashMap
- ConcurrentHashMap是线程安全的Map实现类,还有一个hashTable,还可以使用Collections来包装线程安全的Map。后两者性能比较低()
- ConcurrentHashMap底层数据结构是【数组+链表/红黑树】,支持高并发的访问和更新;通过在部分加锁和利用CAS算法来实现同步,get不加锁
- Node用了volatile修饰。扩容时分配对应的空间,为了防止putVal导致数据不一致,会给所负责的区间加锁
- Jdk7的hashMap扩容时是头插法,JDK8是尾插法
事务问题@Transactional
- 事务失效
看数据库层面 数据库使用的存储引擎是否支持事务===>修改存储引擎为Innodb
默认mysql使用的引擎是innodb,支持事务,如果修改为MyISAM,不支持事务
业务代码层面
使用Spring的声明式事务,看看执行的bean是否已经由Spring管理(@Service,@Component)
@Transactional注解是否被放在了合适的位置(默认情况下无法使用注解对一个非public的方法进行事务管理)
看看是否出现自调用,自调用时,调用的是目标类中的方法而不是代理类中的方法 - 事务回滚相关问题
想回滚的时候事务却提交了
想提交的时候被标记成只能回滚了(rollback only)
默认情况下只有出现RuntimeException 或者Error 时才会回滚
解决:
1、内部事务发生异常,外部事务catch异常后,内部事务自行回滚,不影响外部事务将内部事务的传播级别设置为nested/requires_new均可
2、内部事务发生异常时,外部事务catch异常后,内外两个事务都回滚,但是方法不抛出异常 TransactionInterceptor.currentTransactionStatus().setRollbackOnly(); - 读写分离跟事务结合使用时的问题
读写分离的两种实现方式:
配置多数据源:「如果开启了一个读写事务,那么必须使用写节点」,「如果是一个只读事务,那么可以使用读节点」
依赖中间件(如MyCat):只要开启了事务,事务内的SQL都会使用写节点(依赖于具体中间件的实现,也有可能会允许使用读节点,具体策略需要自行跟DB团队确认)」
微服务
- 微服务之间如何独立通讯的?
同步:dobbo通过 RPC 远程过程调用、springcloud通过 REST 接口json调用 等。
异步:消息队列,如:RabbitMq、ActiveM、Kafka 等。 - SpringCloud 和 Dubbo 有哪些区别?
两者都是分布式管理框架
dubbo 是二进制传输,占用带宽会少一点。SpringCloud是http 传输,带宽会多一点,同时使用http协议一般会使用JSON报文,消耗会更大
dubbo 开发难度较大,所依赖的 jar 包有很多问题大型工程无法解决。SpringCloud 对第三方的继承可以一键式生成,天然集成
SpringCloud 接口协议约定比较松散,需要强有力的行政措施来限制接口无序升级
Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式 - SpringBoot 和 SpringCloud 之间关系?
SpringBoot:专注于快速方便的开发单个个体微服务(关注微观);
SpringCloud:关注全局的微服务协调治理框架,将SpringBoot开发的一个个单体微服务组合并管理起来(关注宏观);
SpringBoot可以离开SpringCloud独立使用,但是SpringCloud不可以离开SpringBoot,属于依赖关系。
关于http
TCP连接的生命周期解读
- 正常 TCP连接
注意: - TCP连接每次均会经过三次握手连接后才能发送数据,经过四次挥手才能断开连接,但是每个TCP 连接在 server 返回 response 后都立马断开,则发起多个 HTTP 请求就要多次创建断开 TCP, 这在 Http 请求很多的情况下无疑是很耗性能的。
- 如果在 server 返回 response 不立即断开 TCP 链接,而是复用这条链接进行下一次的 Http 请求,则无形中省略了很多创建 / 断开 TCP 的开销,性能上无疑会有很大提升。
- 改良TCP连接 (左图是不复用 TCP 发起多个 HTTP 请求的情况,右图是复用 TCP 的情况)
如下图所示,发起三次HTTP请求,复用TCP连接的话可以省去两次建立/断开TCP的开销,理论上一个应用会只需开启一个TCP连接,其他请求复用即可。
- 简单阐述一下keep-alive机制
- keep-alive (又称持久连接,连接复用)做的就是复用连接,保证连接持久有效。
- Http 1.1 之后 keep-alive 默认支持并开启,目前大部分网站都用了 http 1.1 了,也就是说大部分都默认支持连接复用了
- 虽然 keep-alive 省去了很多不必要的握手/挥手操作,但由于连接长期保活,如果一直没有 http 请求的话,这条连接也就长期闲着了,会占用系统资源,有时反而会比复用连接带来更大的性能消耗。
- 一般会为 keep-alive 设置一个 timeout, 这样如果连接在设置的 timeout 时间内一直处于空闲状态(未发生任何数据传输),经过 timeout 时间后,连接就会释放,就能节省系统开销。
- 但是 如果服务端关闭连接,发送 FIN 包(注:在设置的 timeout 时间内服务端如果一直未收到客户端的请求,服务端会主动发起带 FIN 标志的请求以断开连接释放资源),在这个 FIN 包发送但是还未到达客户端期间,客户端如果继续复用这个 TCP 连接发送 HTTP 请求报文的话,服务端会因为在四次挥手期间不接收报文而发送 RST 报文给客户端,客户端收到 RST 报文就会提示异常 (即 NoHttpResponseException)。
- 解决NoHttpResponseException 有两种策略
(a)重试,收到异常后,重试一两次,由于重试后客户端会用有效的连接去请求,所以可以避免这种情况,不过一次要注意重试次数,避免引起雪崩
(b)设置一个定时线程,定时清理上述的闲置连接,可以将这个定时时间设置为 keep alive timeout 时间的一半以保证超时前回收。【evictExpiredConnections使用的就是这种策略:调用这个方法只会产生一个定时线程,那为啥应用中线程会一直增加呢,因为我们对每一个请求都创建了一个 HttpClient! 这样由于创建每一个 HttpClient 实例j时都会调用 evictExpiredConnections ,导致有多少请求就会创建多少个定时线程!】
- 解决问题
- 首先把 HttpClient 改成了单例,这样保证服务启动后只会有一个定时清理线程
- 对应用的线程数做监控,如果超过某个阈值直接告警