面试题总结(三)

本文详细探讨了Java并发控制的关键概念,包括synchronized与Lock的差异,volatile关键字的作用,以及HashMap的底层实现和扩容机制。此外,还介绍了线程池的核心参数,JVM内存溢出的排查方法,以及设计秒杀系统的注意事项。文章深入浅出,旨在帮助读者掌握Java并发编程和数据结构的基础知识。
摘要由CSDN通过智能技术生成

synchornized和lock的区别

  • 存在层次
    synchornized是java的关键字,在jvm层;底层是通过monitor对象来完成
    lock是一个类,是api层面的锁
  • 使用方法
    synchornized以获取锁的线程执行完同步代码后释放锁,不需要手动释放锁
    lock需要手动释放,否则会造成死锁;需要使用lock/unlock结合try/finally来进行上锁或释放锁
  • 锁的类型
    synchornized是可重入、不可中断的非公平锁
    lock是可重入,可中断的公平/非公平锁
  • 调度机制
    synchronized使用Object对象本身的wait 、notify、notifyAll调度机制
    lock可以使用Condition进行线程之间的调度
    这种机制下,lock可以精确的唤醒某一个线程,而synchornized不行
  • 性能
    synchronized在线程竞争不激烈的情况下,通过偏向锁和轻量级锁来优化,适合少量同步的场景
    lock只有重量级锁,通过读写锁等适应不同的场景

volatile关键字有什么用处?用在哪里?

1、保证内存可见性;它可以保证当A线程对变量的值做了变动之后,会立即刷回到主内存中,而其它线程读取到该变量的值也作废,强迫重新从主内存中读取该变量的值,这样在任何时刻,AB线程总是会看到变量的同一个值。
2、禁止指令重排;volatile关键字修饰后,可以保证对变量的操作不会被JVM所重排序,每个线程都是按照顺序执行的
3、但volatile不保证原子性:
举个例子

volatile static int num = 0;

开启多线程的情况下对num做num++操作,num++不是原子操作,它是由三步组成的
首先获取num的值
将该num+1
将num的值写回主存中
每次获取num值的时候,都拿到的是主内存的最新变量值,但是在进行第二步num+1的时候,可能其他线程在此期间已经对num做了修改,这时候就会触发MESI协议的失效动作,将该线程内部的值作废。那么该次+1的动作就会失效了,也就是少加了一次1

HashMap底层数据结构是什么?扩容机制是如何实现的?HashMap怎么解决碰撞问题的?为什么hashmap的负载因子是0.75?为什么HashMap底层树化标准的元素个数是8

JDK 1.8 以前 HashMap 的实现是 数组+链表
JDK 1.8 中引入了 红黑树

当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的2倍。

负载因子是0.75,这是一个“哈希冲突”和“空间利用率”矛盾的一个折衷。负载因子是表示Hsah表中元素的填满的程度。
负载因子越大,填满的元素越多,空间利用率越高,但冲突的机会加大了。
反之,负载因子越小,填满的元素越少,冲突的机会减小,但空间浪费多了。
冲突的机会越大,则查找的成本越高。反之,查找的成本越小。

在理想情况下,使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布,同时给出了桶中元素个数和概率的对照表。

0: 0.60653066
1: 0.30326533
2: 0.07581633
3: 0.01263606
4: 0.00157952
5: 0.00015795
6: 0.00001316
7: 0.00000094
8: 0.00000006

从上面的表中可以看到当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。

设计模式有哪些?你实际使用过哪些?

策略 模板方法 单例 门面模式 创建者模式

线程池的几个核心参数是什么

  • 核心线程数

  • 最大线程数

  • 空闲线程存活时间

  • 工作队列
    SynchronousQuene 无界队列,直接提交队列
    ArrayBlockingQueue 有界队列
    LinkedBlockingQuene 无界阻塞队列
    PriorityBlockingQueue 优先级任务队列
    DelayedQueue 无界队列
    LinkedTransferQueue 无界队列

  • 拒绝策略
    AbortPolicy(默认):丢弃任务并抛出RejetedExecutionException异常
    DiscardPolicy:丢弃任务,但不抛出异常
    DiscardOldestPolicy:抛弃队列中最早的任务,然后将当前任务加入到队列
    CallerRunsPolicy:调用任务的Run方法绕过线程池直接执行

  • 线程工厂

JVM内存溢出了,如何排查问题?

用visualVM工具分析堆快照
如果发生内存泄漏:

  • 找出泄漏的对象
  • 找到泄漏对象的GC Root
  • 根据泄漏对象和GC Root找到导致内存泄漏的代码
  • 想法设法解除泄漏对象与GCRoot的连接

如果不存在泄漏:
看下是否能增大jvm堆的最大容量;优化程序,减小对象的生命周期

怎么做JVM调优?

  • 监控GC的状态
    使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化。
  • 生成堆的dump文件
    通过JMX的MBean生成当前的堆(Heap)信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。
  • 分析dump文件
    打开这个3G的堆信息文件,显然一般的Window系统没有这么大的内存,必须借助高配置的Linux,几种工具打开该文件: Visual VM、IBM HeapAnalyzer、JDK 自带的Hprof工具、Mat(Eclipse专门的静态内存分析工具)推荐使用。
  • 分析结果,判断是否需要优化
    如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化,如果GC时间超过1-3秒,或者频繁GC,则必须优化。
    如果满足下面的指标,则一般不需要进行GC:
    Minor GC执行时间不到50ms;
    Minor GC执行不频繁,约10秒一次;
    Full GC执行时间不到1s;
    Full GC执行频率不算频繁,不低于10分钟1次;

Spring Cloud组件有哪些,自己对哪个最了解,讲解一下?

  • Spring Cloud Eureka
    Eureka负责服务的注册于发现,Eureka的角色和 Zookeeper的角色差不多,都是服务的注册和发现,构成Eureka体系的包括:服务注册中心、服务提供者、服务消费者。
  • Spring Cloud Ribbon
    服务消费者调用服务生产者提供的数据是通过Spring Cloud Ribbon来实现的。
  • Spring Cloud Feign
    一个声明web服务客户端,这使得编写Web服务客户端更容易,使用Feign 创建一个接口并对它进行注解,它具有可插拔的注解支持包括Feign注解与JAX-RS注解,Feign还支持可插拔的编码器与解码器,Spring Cloud 增加了对 Spring MVC的注解,Spring Web 默认使用了HttpMessageConverters, Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端 Feign。
    Spring Cloud Feign 的出现使得Eureka和Ribbon的使用更为简单。
  • Spring Cloud Hystrix
    当有一个服务出现了故障,而服务的调用方不知道服务出现故障,若此时调用放的请求不断的增加,最后就会等待出现故障的依赖方 相应形成任务的积压,最终导致自身服务的瘫痪。
    Spring Cloud Hystrix正是为了解决这种情况的
  • Spring Cloud Config
    分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件Spring Cloud Config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在Cpring Cloud Config 组件中,分两个角色,一是Config Server,二是Config Client。
  • Spring Cloud Zuul
    为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
  • Spring Cloud Bus
    Spring Cloud Bus提供一种操作使得我们在不关闭服务的情况下更新我们的配置。
    Spring Cloud Bus官方意义:消息总线。

简要描述SQL语句执行过程?

  • 客户端发送一条查询给服务器。
  • 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。
  • 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。
  • MySQL根据优化器生成的执行计划,再调用存储引擎的API来执行查询。
  • 将结果返回给客户端。

mysql的索引引擎,有哪些,有什么区别?

MyISAM是非集聚引擎,支持全文索引;不支持事务;它是表级锁;会保存表的具体行数.
每个MyISAM在磁盘上存储成三个文件。文件名都和表名相同,扩展名分别是.frm(存储表定义)、.MYD(MYData,存储数据)、.MYI(MYIndex,存储索引)。数据文件和索引文件可以放置在不同的目录,平均分布io,获得更快的速度。

innoDB是集聚引擎,5.6以后才有全文索引;支持事务;它是行级锁;不会保存表的具体行数.
不用事务的时候,count计算多的时候适合myisam引擎。对可靠性要求高就是用innodby引擎。

InnoDB 支持ACID事务,支持事务的四种隔离级别,串行化,可重复读,读已提交,读未提交。
支持行级锁以及外检约束:所以可以支持写并发。
不存储总行数
逐渐索引采用聚集索引,索引的数据域存储数据文件本身。

sql优化思路?mysql本身调优 数据量大的化 分库分表 es

  • 先对查询进行优化,尽量避免全表扫描,在查询条件上创建索引
  • 表的列数过多的情况下做表的拆分
  • 数据量大的情况下可以考虑分库分表
  • 对于一些复杂的查询,MySQL支持得不够友好,可以考虑es

binlog、redolog有什么区别?

  • redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
  • redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑。
  • redo log 是循环写的,空间固定会用完(4个文件,每个文件1G);binlog 是可以追加写入的。“追加写”是指 binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
  • binlog 日志没有 crash-safe 的能力,只能用于归档。而 redo log 来实现 crash-safe 能力。
  • redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。
  • sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。
  • binlog有两种模式,statement 格式是记sql语句, row格式是记录行的内容,记两条,更新前和更新后都有。

是否用过分库分表,主流分库分表中间件是如何选型的?

分库分表面对的3个问题:

  • 事务一致性:比如更新10张表,最后一张失败,怎样保证事务。

  • 字典表问题:一般字典表维护在一个库中,分库查询的话影响效率,每个库都存储一份字典表的话,上表面提到的事务一致性问题又会出现。库之间也会过于冗余。

  • 分页查询:比如查询100到110之间的数据,做法可不是每个库取100-110间的数据,再去前10条,而是每个库查询0-110间的数据,比如10个库,就会返回 10 * 110条数据,再从这里取100-110间的数据。这里的问题就是如果是 500000~500010的话,返回的数据量就太大了。

Atlas:不能实现分布式分表,所有的子表必须在同一台DB的同一个database里且所有的子表必须事先建好,Atlas没有自动建表的功能。Atlas参考链接
Cobar:必须将拆分后的表分别放入不同的库来实现分布式。
TDDL:阿里,功能强大,过于复杂,部分开源。需要评估使用情况,防止过剩。
Mycat :国内开源,从入门到放弃。
heisenberg:百度开源,相对简单,易于管理。
Oceanus:功能强大,开源,简化开发和配置成功。但产品还不成熟。
vitess:google产品,集群基于ZooKeeper管理,通过RPC方式进行数据处理,可支撑高流量,它还添加了一个连接池,具有基于行的高速缓存,重写SQL查询,更安全。
OneProxy:中国厂商产品,稳定性待确认。
Sharding-JDBC:当当最新开源,jdbc层面操作。

Redis的5种基础数据类型及其对应数据结构?

字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)

redis和memcached、mongodb的区别

  • 性能对比:
    三者的性能都比较高,性能对比的话:Memcache和Redis差不多,要高于MongoDB。
    便捷性对比
    memcache数据结构单一;
    redis丰富一些,数据操作方面,redis更好一些,较少的网络IO次数;
    mongodb支持丰富的数据表达、索引,最类似关系型数据库,支持的查询语言非常丰富。

  • 存储空间对比
    redis在2.0版本后增加了自己的VM特性,突破物理内存的限制,可以对key value设置过期时;
    memcache可以修改最大可用内存,采用LRU算法;
    mongoDB适合大数据量的存储,依赖操作系统VM做内存管理,吃内存也比较厉害,服务不要和别的服务在一起。

  • 可用性对比
    redis依赖客户端来实现分布式读写。主从复制时,每次从节点重新连接主节点都要依赖整个快照,无增量复制,因性能和效率问题,所以单点问题比较复杂,不支持自动sharding,需要依赖程序设定一致hash机制。一种替代方案是不用redis本身的复制机制,采用自己做主动复制(多份存储),或者改成增量复制的方式(需要自己实现),一致性问题和性能的权衡。
    Memcache本身没有数据冗余机制,也没必要;对于故障预防,采用依赖成熟的hash或者环状的算法,解决单点故障引起的抖动问题。
    mongoDB支持master-slave replicaset (内部采用paxos选举算法,自动故障恢复)、auto sharding机制,对客户端屏蔽了故障转移和切分机制。

  • 可靠性对比:
    redis支持(快照、AOF)依赖快照进行持久化,aof增强了可靠性的同时,对性能有所影响;
    memcache不支持,通常用在做缓存,提升性能;
    MongoDB从1.8版本开始采用binlog方式支持持久化的可靠性。

  • 一致性对比
    Memcache在并发场景下,用cas保证一致性;
    redis事务支持比较弱,只能保证事务中的每个操作连续执行;
    mongoDB不支持事务。

  • 数据分析
    mongoDB内置了数据分析的功能(mapreduce),其他两者不支持。

  • 应用场景:
    redis:数据量较小的更性能操作和运算上;
    memcache:用于在动态系统中减少数据库负载,提升性能。做缓存,提高性能(适合读多写少,对于数据量比较大,可以采用sharding);
    MongoDB:主要解决海量数据的访问效率问题。

树、链表、数组,查询时间复杂度分别是多少?

在这里插入图片描述

设计一个"秒杀系统",讲一下如何实现,使用哪些技术,有什么注意点?

  • 秒杀系统场景特点

    • 秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增。
    • 秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功。
    • 秒杀业务流程比较简单,一般就是下订单减库存。
  • 秒杀架构设计理念

    • 限流: 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。
    • 削峰:对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。
    • 异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。
    • 内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。
    • 可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了。像淘宝、京东等双十一活动时会增加大量机器应对交易高峰。
  • 前端方案

    • 浏览器端(js):
      页面静态化:将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。
      禁止重复提交:用户提交之后按钮置灰,禁止重复提交
      用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流
  • 后端方案

    • 服务端控制器层(网关层)
      限制uid(UserID)访问频率:我们上面拦截了浏览器访问的请求,但针对某些恶意攻击或其它插件,在服务端控制层需要针对同一个访问uid,限制访问频率。
    • 服务层
      上面只拦截了一部分访问请求,当秒杀的用户量很大时,即使每个用户只有一个请求,到服务层的请求数量还是很大。比如我们有100W用户同时抢100台手机,服务层并发请求压力至少为100W。
      采用消息队列缓存请求:既然服务层知道库存只有100台手机,那完全没有必要把100W个请求都传递到数据库啊,那么可以先把这些请求都写到消息队列缓存一下,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
      利用缓存应对读请求:对类似于12306等购票业务,是典型的读多写少业务,大部分请求是查询请求,所以可以利用缓存分担数据库压力。
      利用缓存应对写请求:缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。
    • 数据库层
      数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值