面试有关内容

1. 面试说明

1.1 面试技巧

  • 面试前

    • 最好去查下公司相关产品业务、如果有app或者网站,提前去熟悉
    • 巩固面试岗位所需要的技术栈,有自己擅长的某一个领域 比如某个中间件或者框架
  • 面试中

    • 要有礼貌,保持头脑清晰,思维清晰
    • 要有自信,不能自卑或者没信心
    • 专业技术一定要准备好,尽量往自己熟悉的方向考虑
    • 做过项目一定要熟悉,准备好项目的亮点和难点
    • 表现出有学习热情,掌握主流技术栈,关注前言技术
  • 面试后

    • 一定要做复盘
    • 面试多了,问题都类似,一定去补上,下次也可能遇到
    • 可以问问面试官有没回答不好的点

1.2 非技术面试问题回答注意点

  • 做下简单的自我介绍?

    • 基本信息、工作经历等,2-3分钟即可
  • 为什么从上个公司离职?

    • 不能说不好的,比如公司混乱、勾心斗角、负面情绪
    • 推荐说公司搬迁比较远,然后刚好个人职业规划对某某领域比较喜欢
  • 你对加班的是怎么看的?

    • 业务发展好、公司快速发展,项目紧急等加班非常乐意

    • 自己也会合理评估工作,提高个人工作效率

  • 有没女朋友,家住在哪里?

    • 考查是否上班太远了,浪费时间,且容易换工作
  • 你对自己未来职业规划是怎样的?

    • 往专业上靠拢,比如几年发展成技术负责人,带团队,多少年称为架构师, 比较喜欢看技术书籍提升能力
  • 你认为自己有什么缺点?

    • 不能说没有缺点
    • 避免说影响工作、让人觉得不靠谱
    • 可以说些表面上看是缺点,从工作的角度看却是优点的缺点:对事情追求比较高,比如代码洁癖爱好者,对自己做的产品比较有要求,但也会适当控制
  • 平时有什么爱好或者兴趣?

    • 不能说抽烟喝酒啥的
    • 建议是积极向上的活动
  • 你期望的薪资是多少?

    • 薪酬最好写个范围,也看公司具体有哪些福利,比如餐补、交通、住房补助等,是否有13薪或者其他福利
  • 你还有其他问题想问的吗?

    • 公司是否有员工培训、晋升机制等
    • 自己面试是否有不足,自己好改进
    • 什么时候会有面试结果,能不能加个联系方式

2. 技术点

2.1 Java集合框架之List

2.1.1 能否说下Vector和ArrayList、LinkedList联系和区别?分别的使用场景
1. 线程安全
  ArrayList:底层是数组实现,线程不安全,查询和修改非常快,但是增加和删除慢
  LinkedList: 底层是双向链表,线程不安全,查询和修改速度慢,但是增加和删除速度快
  Vector: 底层是数组实现,线程安全的,操作的时候使用synchronized进行加锁
2. 使用场景
  Vector已经很少用了
  增加和删除场景多则用LinkedList
  查询和修改多则用ArrayList
2.1.2 如果需要保证线程安全,ArrayList应该怎么做,用有几种方式
方式一:自己写个包装类,对相应的操作进行加锁

方式二:Collections.synchronizedList(new ArrayList<>()); 使用synchronized加锁

方式三:CopyOnWriteArrayList<>()  使用ReentrantLock加锁
2.1.3 了解CopyOnWriteArrayList吗?和 Collections.synchronizedList实现线程安全有什么区别, 使用场景是怎样的?
CopyOnWriteArrayList:执行修改操作时,会拷贝一份新的数组进行操作(add、set、remove等),代价十分昂贵,在执行完修改后将原来集合指向新的集合来完成修改操作,源码里面用ReentrantLock可重入锁来保证不会有多个线程同时拷贝一份数组

场景:读高性能,适用读操作远远大于写操作的场景中使用(读的时候是不需要加锁的,直接获取,删除和增加是需要加锁的, 读多写少)
 

Collections.synchronizedList:线程安全的原因是因为它几乎在每个方法中都使用了synchronized同步*锁

场景:写操作性能比CopyOnWriteArrayList好,读操作性能并不如CopyOnWriteArrayList
2.1.4 CopyOnWriteArrayList的设计思想是怎样的,有什么缺点?
答案:设计思想:读写分离+最终一致

缺点:内存占用问题,写时复制机制,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象,如果对象大则容易发生Yong GC和Full GC

2.2 Java集合框架之Map

2.2.1 了解Map吗?用过哪些Map的实现
了解,Map的实现类有 
Hashtable:他是java很早之前提供的一个哈希表的实现,它是线程安全的,但不支持null键和值,因为它的性能不如ConcurrentHashMap,所以平常很少被推荐使用。
HashMap:最常用的哈希表实现,如果程序不需要使用多线程,它是个很好的选择,支持null键和值,如果在多线程的情况下,可以使用ConcurrentHashMap替代。
TreeMap:它是基于红黑树的一种提供顺序访问的Map,自身实现了key的自然排序,也可以指定Comparator来自定义排序。
LinkedHashMap:它是HashMap的一个子类,保存了记录的插入顺序,也可以指定Comparator来自定义排序。
2.2.2 说下 HashMap和Hashtable 的区别
HashMap:底层是基于数组+链表,非线程安全的,默认容量是16、允许有空的健和值  x%2^n = x & (2^n-1) 
Hashtable:基于哈希表实现,线程安全的(加了synchronized),默认容量是11,不允许有null的健和值

从相同点和不同点回答,他们的相同点是它们都实现了Map、Cloneable(可克隆)、Serializable(可序列化)这三个接口。
    不同点是:数据结构上不同,在jdk1.7之前是数组加链表的形式,但jdk1.8之后HashMap使用了红黑树,Hashtable 是不允许键或值为 null 的,但HashMap 是允许键或值为 null 的。在添加key-value的hash值算法不同,HashMap添加元素时,是使用自定义的哈希算法,而HashTable是直接采用key的HashCode()。它们间实现方式不同,hashtable继承的是Dictionary类,而HashMap继承的是AbstractMap类,初始化容量,HashMap的初始化容量是16,Hashtable的初始化容量是11,两者的负载因子默认为0.75,扩容机制也不同,当已用容量>总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 +1。          支持的遍历种类也不同,HashMap只支持Iterator遍历,而HashTable支持Iterator和Enumeration两种方式遍历,        迭代器不同:HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。而Hashtable 则不会。
2.2.3 HashMap和TreeMap应该怎么选择,使用场景
hashMap: 散列桶(数组+链表),可以实现快速的存储和检索,但是确实包含无序的元素,适用于在map中插入删除和定位元素

treeMap:使用存储结构是一个平衡二叉树->红黑树,可以自定义排序规则,要实现Comparator接口
能便捷的实现内部元素的各种排序,但是一般性能比HashMap差,适用于安装自然排序或者自定义排序规则
2.2.4 如果需要线程安全的Map,应该怎么做?
多线程环境下可以用concurrent包下的ConcurrentHashMap, 或者使用Collections.synchronizedMap(),

ConcurrentHashMap虽然是线程安全,但是他的效率比Hashtable要高很多
2.2.5 为什么Collections.synchronizedMap后是线程安全的?
使用Collections.synchronizedMap包装后返回的map是加锁的
2.2.6 谈一谈你对HashMap的理解
HashMap是一个Java中一个键值对容器(集合),是Map接口的一个具体实现,底层是用哈希表实现,在JDK1.7之前是数组+链表,JDK1.8之后,是数组+链表+红黑树,当链表长度大于8,数组长度大于64时会转化为红黑树。
2.2.7 能否解释下什么是Hash碰撞?常见的解决办法有哪些,hashmap采用哪种方法
hash碰撞的意思是不同key通过Hash之后落到相同的哈希槽
    
常见的解决办法:链表法、开放寻址法、多重哈希法等
    
HashMap采用的是链表法
hashcode && (== || equals)

假设y=2^n
x % y = x & (y - 1)
2.2.8你说HashMap底层是 数组+链表+红黑树,为什么要用这几类结构呢?
数组 Node<K,V>[] table ,哈希表,根据对象的key的hash值进行在数组里面是哪个节点   put(k,v)  Node  Entry 
 
链表的作用是解决hash冲突,将hash值取模之后的对象存在一个链表放在hash值对应的槽位
 
 红黑树 JDK8使用红黑树来替代超过8个节点的链表,主要是查询性能的提升,从原来的O(n)到O(logn),
 通过hash碰撞,让HashMap不断产生碰撞,那么相同的key的位置的链表就会不断增长,当对这个Hashmap的相应位置进行查询的时候,就会循环遍历这个超级大的链表,性能就会下降,所以改用红黑树
2.2.9 为啥选择红黑树而不用其他树,比如二叉查找树,为啥不一直开始就用红黑树,而是到8的长度后才变换
二叉查找树在特殊情况下也会变成一条线性结构,和原先的链表存在一样的深度遍历问题,查找性能就会慢,
使用红黑树主要是提升查找数据的速度,红黑树是平衡二叉树的一种,插入新数据后会通过左旋,右旋、变色等操作来保持平衡,解决单链表查询深度的问题​
数据量少的时候操作数据,遍历线性表比红黑树所消耗的资源少,且前期数据少 平衡二叉树保持平衡是需要消耗资源的,所以前期采用线性表,等到一定数之后变换到红黑树
2.2.10 说下hashmap的put和get的核心逻辑
n % 2^n  =  n & (2^n - 1)

hashcode && (== || equals)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mKfKjgZd-1653878038343)(C:\Users\chenxiuser\AppData\Roaming\Typora\typora-user-images\image-20220503105315421.png)]

2.2.11 了解ConcurrentHashMap吗?为什么性能比hashtable高,说下原理
ConcurrentHashMap线程安全的Map, hashtable类基本上所有的方法都是采用synchronized进行线程安全控制  ReentrantLock
高并发情况下效率就降低​   
ConcurrentHashMap是采用了分段锁的思想提高性能,锁粒度更细化
2.2.12 jdk1.7和jdk1.8里面ConcurrentHashMap实现的区别有没了解
JDK8之前,ConcurrentHashMap使用锁分段技术,将数据分成一段段存储,每个数据段配置一把锁,即segment类,这个类继承ReentrantLock来保证线程安全
技术点:Segment+HashEntry​
JKD8的版本取消Segment这个分段锁数据结构,底层也是使用Node数组+链表+红黑树,从而实现对每一段数据就行加锁,也减少了并发冲突的概率,CAS(读)+Synchronized(写)
技术点:Node+Cas+Synchronized

2.3 Java并发编程

2.3.1 在Java中可以有哪些方法来保证线程安全(原子性、可见性、有序性有这三点可以保证线程安全)
加锁,比如synchronize/ReentrantLock
使用volatile声明变量,轻量级同步,不能保证原子性(需要解释)(不能保证线程安全)
使用线程安全类(原子类AtomicXXX,并发容器,同步容器 CopyOnWriteArrayList/ConcurrentHashMap等
2.3.2 了解volatile关键字不?能否解释下,然后这和synchronized有什么大的区别
volatile是轻量级的synchronized,保证了共享变量的可见性,被volatile关键字修饰的变量,如果值发生了变化,其他线程立刻可见,避免出现脏读现象​
volatile:保证可见性,但是不能保证原子性
synchronized:保证可见性,也保证原子性   happens-before
由于禁止了指令重排,所以JVM相关的优化没了,效率会偏弱

volatile可以保证可见性和有序性,当保证不了原子性,
2.3.3 你说volatile可以避免指令重排,能否解释下什么是指令重排
指令重排序分两类 编译器重排序和运行时重排序​
JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,主要目的是优化运行效率(不改变程序结果的前提)​
2.3.4 你日常开发里面用过java里面有哪些锁?分别解释下
悲观锁:当线程去操作数据的时候,总认为别的线程会去修改数据,所以它每次拿数据的时候都会上锁,别的线程去拿数据的时候就会阻塞,比如synchronized
乐观锁:每次去拿数据的时候都认为别人不会修改,更新的时候会判断是别人是否回去更新数据,通过版本来判断,如果数据被修改了就拒绝更新,比如CAS是乐观锁,但严格来说并不是锁,通过原子性来保证数据的同步,比如说数据库的乐观锁,通过版本控制来实现,CAS不会保证线程同步,乐观的认为在数据更新期间没有其他线程影响
小结:悲观锁适合写操作多的场景,乐观锁适合读操作多的场景,乐观锁的吞吐量会比悲观锁多

公平锁:指多个线程按照申请锁的顺序来获取锁,简单来说 如果一个线程组里,能保证每个线程都能拿到锁 比如ReentrantLock(底层是同步队列FIFO:First Input First Output来实现)
非公平锁:获取锁的方式是随机获取的,保证不了每个线程都能拿到锁,也就是存在有线程饿死,一直拿不到锁,比如synchronized、ReentrantLock
小结:非公平锁性能高于公平锁,更能重复利用CPU的时间


可重入锁:也叫递归锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
不可重入锁:若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
小结:可重入锁能一定程度的避免死锁 synchronized、ReentrantLock 重入锁

自旋锁:一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,任何时刻最多只能有一个执行单元获得锁.
小结:不会发生线程状态的切换,一直处于用户态,减少了线程上下文切换的消耗,缺点是循环会消耗CPU
常见的自旋锁:TicketLock,CLHLock,MSCLock

共享锁:也叫S锁/读锁,能查看但无法修改和删除的一种数据锁,加锁后其它用户可以并发读取、查询数据,但不能修改,增加,删除数据,该锁可被多个线程所持有,用于资源数据共享

互斥锁:也叫X锁/排它锁/写锁/独占锁/独享锁/ 该锁每一次只能被一个线程所持有,加锁后任何线程试图再次加锁的线程会被阻塞,直到当前线程解锁。例子:如果 线程A 对 data1 加上排他锁后,则其他线程不能再对 data1 加任何类型的锁,获得互斥锁的线程即能读数据又能修改数据

死锁:两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法让程序进行下去

下面三种是Jvm为了提高锁的获取与释放效率而做的优化 针对Synchronized的锁升级,锁的状态是通过对象监视器在对象头中的字段来表明,是不可逆的过程,
偏向锁:一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,获取锁的代价更低,
轻量级锁:当锁是偏向锁的时候,被其他线程访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,但不会阻塞,且性能会高点
重量级锁:当锁为轻量级锁的时候,其他线程虽然是自旋,但自旋不会一直循环下去,当自旋一定次数的时候且还没有获取到锁,就会进入阻塞,该锁升级为重量级锁,重量级锁会让其他申请的线程进入阻塞,性能也会降低
2.3.5 对synchronized了解不,能否介绍下你对synchronized的理解
synchronized是解决线程安全的问题,常用在 同步普通方法、静态方法、代码块 中

非公平、可重入

每个对象有一个锁和一个等待队列,锁只能被一个线程持有,其他需要锁的线程需要阻塞等待。锁被释放后,对象会从队列中取出一个并唤醒,唤醒哪个线程是不确定的,不保证公平性
2.3.6 jdk1.6后进行了优化,你知道哪些大的变化
有得到锁的资源进入Block状态,涉及到操作系统用户模式和内核模式的切换,代价比较高
jdk6进行了优化,增加了从偏向锁到轻量级锁再到重量级锁的过渡,但是在最终转变为重量级锁之后,性能仍然较低
2.3.7 了解CAS不,能否解释下什么是CAS
全称是Compare And Swap,即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。
底层通过Unsafe类实现原子性操作操作包含三个操作数 —— 内存地址(V)、预期原值(A)和新值(B)。 
如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 ,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
CAS这个是属于乐观锁,性能较悲观锁有很大的提高
AtomicXXX 等原子类底层就是CAS实现,一定程度比synchonized好,因为后者是悲观锁  i++
2.3.8 CAS会存在什么比较严重的问题?
1、自旋时间长CPU利用率增加,CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用

2、存在ABA问题 
2.3.9 能否解释下什么是ABA问题,怎么避免这个问题呢?
如果一个变量V初次读取是A值,并且在准备赋值的时候也是A值,那就能说明A值没有被修改过吗?其实是不能的,因为变量V可能被其他线程改回A值,结果就是会导致CAS操作误认为从来没被修改过,从而赋值给V

解决方案:给变量加一个版本号即可,在比较的时候不仅要比较当前变量的值 还需要比较当前变量的版本号。
在java5中,已经提供了AtomicStampedReference来解决问题,检查当前引用是否等于预期引用,其次检查当前标志是否等于预期标志,如果都相等就会以原子的方式将引用和标志都设置为新值
2.3.10 ReentrantLock和synchronized使用的场景是什么,实现机制有什么不同
ReentrantLock和synchronized都是独占锁

synchronized:
    1、是悲观锁会引起其他线程阻塞,java内置关键字,
    2、无法判断是否获取锁的状态,锁可重入、不可中断、只能是非公平
    3、加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单但显得不够灵活
    4、一般并发场景使用足够、可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁
    5、synchronized操作的应该是对象头中mark word

ReentrantLock:
    1、是个Lock接口的实现类,是悲观锁,
    2、可以判断是否获取到锁,可重入、可判断、可公平可不公平
    3、需要手动加锁和解锁,且 解锁的操作尽量要放在finally代码块中,保证线程正确释放锁
    4、在复杂的并发场景中使用在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。
    5、创建的时候通过传进参数true创建公平锁,如果传入的是false或没传参数则创建的是非公平锁
    6、底层不同是AQS的state和FIFO队列来控制加锁
2.3.11 ThreadPoolExecutor构造函数里面的参数你是否掌握,能否解释下各个参数的作用
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值