Java开发工程师面试总结
1. Java基础
1.1 接口与抽象类的区别
- 接口里必须是抽象方法,抽象类可以没有抽象方法;
- 接口可以继承多个父接口,抽象类只能被单继承;
- 接口里的变量必须被static,final修饰并初始化,抽象类里可以普通的成员变量;
1.2 重写与重载的区别
- 重载是表示一个类中可以有多个方法名相同,参数不同(参数个数,参数类型,参数顺序不同);
- 重写是子类对父类方法的一种重写, 访问权限不能是private的;
1.3 集合
Map和collection是所有集合的父接口;
- Collection-set/List
- List-Vector,ArrayList,LinkedList 有序的,可以重复
- set-HashSet,TreeSet,LinkedHashSet 无序的,不可以重复
- Map
- HashMap线程不同步,线程不安全,允许null作为key;
- HashTable使用了syschonized,线程安全,每次需要锁住整个结构,不允许null作为key;
- ConcurrentHashMap锁的粒度很细,分为16个桶,只锁当前用到的桶;
HashMap的底层原理:
Java8之前是数组+链表,Java8之后是数组+链表+红黑树;
数组中都是key-value的实例,java8之前叫entry,Java8之后叫node;
put的时候是通过key的hash值去计算index位置;
数组的长度是有限的,当hash值一样时,就有了链表;
扩容机制:负载因子是0.75f,创建新数组,长度为原来的2倍,重新计算hash值然后插入,长度扩大后,hash规则随之变化;
插入链表的方式Java8之前是头插法,Java8后是尾插法;头插会改变链表的顺序,尾插则不会,就不会出现链表成环的问题,死循环;
HashMap不能用在多线程中,底层代码get/put并没有加同步锁,线程不安全;
初始化长度为16,源代码中1<<4 为16;
Q:为啥我们重写equals方法的时候需要重写hashCode方法呢?用HashMap说明一下?
为重写equals方法时,我们用的object类的equals方法,比较的是两个对象的内存地址,计算key的hashCode值来定位index,当值相同时,利用equals方法判断是否相同,重写hashCode方法,保证相同的对象返回相同的hash值;
hash冲突,利用链表来解决,当碰撞发生时,对象会被存储在链表的下一节点;
原理是 首先当调用put方法时,用hashCode方法计算index(bucket桶)的位置来存储对象,获取对象时用equals方法找到正确的对象;
ConcurrentHashMap的实现原理:hashEntry用来封装表的键值对和segment可重入锁,每个segment保护一个HashEntry数组的元素;java7实行分段锁;java8采用table数组作为锁,对每一行进行加锁,数组,链表加红黑树(超过长度后就到红黑树)结构,采用Synchronize进行同步锁粒度降低和CAS
1.4 多线程
创建线程的方式
- 继承Thread类
- 实现Runnable接口,无返回结果
- 实现Callable接口,有返回值
调用Thread.start()启动线程,run()执行线程的运行代码;
线程和进程的区别
进程是一个系统进行资源分配的独立单位
线程是进程的一个实体。一个进程可以有多个线程;
线程是怎样造成死锁的?
- 一个资源每次只能被一个线程使用;
- 线程已经获得的资源。在未使用完前,不能进行剥夺;
- 线程在请求资源阻塞时,对已有的资源保持不放;
- 若干线程循环等待资源关系;
如何避免死锁?
指定获取锁的顺序;只有获取A锁的线程才能获取B锁;
sleep()和wait()的区别?
sleep()是Thread类的方法,睡眠多少毫秒,线程阻塞,到时间会接触阻塞,阻塞时不会释放锁;
wait()是Object类的方法,必须与synchronized一起使用,线程阻塞时会释放互斥锁,notify()唤醒线程;
线程池
四种线程池
- newFixedThreadPool 固定线程数目的
- newCachedThreadPool 可缓存的
- newSingleThreadPool 一个单线程的
- newScheduledThreadPool 支持定时及周期性的任务执行的
创建线程池的方式
用Excutors类的四个方法,ExecutorService;
自己定义newThreadPoolExecutor;构造方法参数说明
- corePoolSize 核心线程数
- maximumPoolSize 线程池最大容纳线程数
- keepAliveTime 非核心线程的闲置超时时间
- unit 指定keepAliveTime 单位
- workQueue 线程队列 linkedBlockingQueue Array…
ThreadFactoryBuilder;
保证多线程安全
使用安全类 java.util.concurrent下的原子类AtomicInteger
concurrentHashMap
1.6 反射
运行时可以获取类的信息(属性,构造器,方法,注解);运行时动态创建对象;
Class clazz = Class.forName(className)
Constuctor constructor = clazz.getConstuctor();
constructor.newInstance();
Class<> xxx = XX.class; 已经声明过类型;
Class<> xx = xx.getClass(); 获取该实例的对象
Class.forName() 通过全限定名来获取对象
场景
spring读取配置文件到容器
jdbc连接数据库驱动类时
2. Spring框架
2.1 IOC
2.2 AOP
2.3 事务
3. Spring Cloud微服务
4.MySQL数据库
4.1 SQL调优
创建索引,避免全表扫描,经常检索的字段创建索引;
避免在索引上使用计算;
使用表的别名
考虑创建中间表
4.2 数据库性能优化方案
- 代码层面的优化 ;
- 定位慢sql ,慢查询日志定位到出问题的sql,使用explain工具进行比对和调优;
- 合理使用索引,可以使用的操作符 between, in, >,like不以%开头,不能用的操作符 not in, like以%开头;
- 容易导致索引失效 ,避免使用or来连接,不做列运算
- 数据超百万可能需要分库分表;
- 缓存查询,先到缓存查询,再到数据库查询;
4.3 NoSQL的特性和使用场景
对exception做的监控系统,如果在应用系统发生严重故障的时候,可能会短时间产生大量exception数据,这个时候如果选用MySQL,会造成MySQL的瞬间写压力飙升,容易导致MySQL服务器的性能急剧恶化以及主从同步延迟之类的问题,这种场景就比较适合用Hbase类似的NoSQL来存储。
5.分布式
5.1 分布式缓存
静态RAM加速,通过内存或其他高速存储来加速;
缓存更新模式
- Cache aside 缓存读取失败,查询db,写入缓存;更新:直接更新到DB;
- read/write through 读取写入都是用了缓存存储;
- write behind caching 这种模式下所有的操作都走缓存,缓存里的数据再通过异步的方式同步到数据库里面。所以系统的写性能能够大大提升了。
缓存失效策略
- 主动失效 系统自带主动检查是否失效的机制
- 被动失效 通过访问缓存对象才去检查是否失效
缓存淘汰策略
- FIFO 先进先出
- LRU 最近最久没有使用
- LFU 最近最少使用
分布式缓存常见问题
- 缓存穿透 db中不存在数据,穿过缓存查db,网络攻击;
解决:包装对象; - 缓存击穿 在缓存失效的时候,大量的请求,造成db压力瞬间过大;
解决:在查询db前,使用分布式锁锁住服务; - 缓存雪崩 大部分缓存同时失效,造成服务器性能急剧下降;
解决:失效时间为基本时间+随机时间;