Java面试疑难问题汇总

HashMap和ConcurrentHashMap区别

HashMap

  1. 底层数组+链表实现,可以存储null键和null值,线程不安全
  2. 初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
  3. 扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
  4. 插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
  5. 当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
  6. 计算index方法:index = hash & (tab.length – 1)
  7. HashMap的初始值还要考虑加载因子:哈希冲突:若干Key的哈希值按数组大小取模后,如果落在同一个数组下标上,将组成一条Entry链,对Key的查找需要遍历Entry链上的每个元素执行equals()比较。加载因子:为了降低哈希冲突的概率,默认当HashMap中的键值对达到数组大小的75%时,即会触发扩容。因此,如果预估容量是100,即需要设定100/0.75=134的数组大小。空间换时间:如果希望加快Key查找的时间,还可以进一步降低加载因子,加大初始大小,以降低哈希冲突的概率。
  8. HashMap的内部结构可以看作是数组(Node[] table)和链表的复合结构,数组被分为一个个桶(bucket),通过哈希值决定了键值对在这个数组中的寻址(哈希值相同的键值对,则以链表形式存储)

ConcurrentHashMap

  1. 底层采用分段的数组+链表实现,线程安全
  2. 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
  3. Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
  4. 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
  5. 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
  6. 1.7分段锁,对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。
  7. 1.8 优化的synchronized 关键字同步代码块 和 cas操作了维护并发

ConcurrentHashMap的数据结构

jdk1.7由一个Segment数组和多个HashEntry组成,分段加锁

jdk1.8用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作

高并发HashMap的环是如何产生的?

扩容调用transfer方法,首先获取新表的长度,之后遍历新表的每一个entry,然后每个ertry中的链表,以反转的形式,形成rehash之后的链表。

若当前线程此时获得ertry节点,但是被线程中断无法继续执行,此时线程二进入transfer函数,并把函数顺利执行,此时新表中的某个位置有了节点,之后线程一获得执行权继续执行,因为并发transfer,所以两者都是扩容的同一个链表,当线程一执行到e.next = new table[i] 的时候,由于线程二之前数据迁移的原因导致此时new table[i] 上就有ertry存在,所以线程一执行的时候,会将next节点,设置为自己,导致自己互相使用next引用对方,因此产生链表,导致死循环。

volatile作用

可见(修改推到主存)、防止指令重排(添加内存屏障,防止指令重排)

Atomic类如何保证原子性

atomic 内部的value 使用volatile保证内存可见性,使用CAS(compare and swap)保证原子性。

内存值 V 预估值 A 更新值 B,如果V和A相同时(compare),才会把B赋值给V(swap),否则什么都不做。

synchronized和Lock的区别

  1. Synchronized是关键字,内置语言实现,Lock是接口。
  2. Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
  3. Lock是可以中断锁,Synchronized是非中断锁,必须等待线程执行完成释放锁。
  4. Lock可以使用读锁提高多线程读效率。

为什么要使用线程池?

为了减少创建和销毁线程的次数,让每个线程可以多次使用。可根据系统情况调整执行的线程数量(根据cpu运行时间和等待时间计算),防止消耗过多内存,所以我们可以使用线程池。

java中线程池的顶级接口是Executor,ExecutorService是Executor的子类,也是真正的线程池接口,它提供了提交任务关闭线程池等方法。调用submit方法提交任务还可以返回一个Future(fei 曲儿)对象,利用该对象可以了解任务执行情况,获得任务的执行结果取消任务

由于线程池的配置比较复杂,JavaSE中定义了Executors类就是用来方便创建各种常用线程池的工具类。通过调用该工具类中的方法我们可以创建单线程池(newSingleThreadExecutor),固定数量的线程池(newFixedThreadPool),可缓存线程池(newCachedThreadPool),大小无限制的线程池(newScheduledThreadPool)

比较常用的是固定数量的线程池和可缓存的线程池。

固定数量的线程池是每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。

可缓存线程池是当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。

核心线程池ThreadPoolExecutor的参数

corePoolSize

核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。

核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。

maxPoolSize

当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。

keepAliveTime

当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。

allowCoreThreadTimeout

是否允许核心线程空闲退出,默认值为false。

queueCapacity

任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

ThreadPoolExecutor的工作流程

  1. 核心线程数未满,直接创建新的核心线程来执行任务,若有空闲线程,复用空闲线程
  2. 核心线程数已满,要执行的任务加入堵塞队列
  3. 核心线程数已满,堵塞队列已满,创建非核心线程来执行任务(非时序关联的流程,非核心线程若空闲,并且到达存活时间,会被回收),若有空闲线程,可复用
  4. 核心线程已满,堵塞队列已满,非核心线程已满,任务会被拒绝执行,调用RejectedExecutionHandler

如何控制线程池线程的优先级

Thread.currentThread.setPriority(1)

线程之间如何通信

  1. volatile
  2. wait、notify
  3. countdownlatch和栅栏

java基本类型各占几个字节

  • byte 1字节容
  • short 2字节
  • int 4字节
  • long 8字节
  • float 4字节
  • double 8字节
  • char 2字节
  • boolean 1字节

jdk1.8/jdk1.7都分别新增了哪些特性?

jdk1.7新特性

  1. 泛型实例的创建可以通过类型推断来简化,可以去掉后面new部分的泛型类型,只用<>就可以了。
  2. 并发工具增强: fork-join框架最大的增强,充分利用多核特性,将大问题分解成各个子问题,由多个cpu 可以同时 解决多个子问题最后合并结果,继承RecursiveTask,实现compute方法,然后调用fork计算,最后用join合并结果
  3. try-with-resources语句是一种声明了一种或多种资源的try语句。资源是指在程序用完了之后必须要关闭的对象。try-with-resources语句保证了每个声明了的资源在语句结束的时候都会被关闭。任何实现了java.lang.AutoCloseable接口的对象,和实现了java .io .Closeable接口的对象,都可以当做资源使用
  4. Catch多个异常:在Java 7中,catch代码块得到了升级,用以在单个catch块中处理多个异常。如果你要捕获多个异常并且它们包含相似的代码,使用这一特性将会减少代码重复度。下面用一个例子来理解。

jdk1.8新特性

  1. jdk1.8对hashMap等map集合的优化
  2. Lambda表达式
  3. 函数式接口
  4. 方法引用和构造器调用
  5. Stream API
  6. 并行流和串行流
  7. Optional容器
  8. 接口中的默认方法和静态方法
  9. 新时间日期API
  10. 定义可重复的注解
  11. 扩展注解的支持
  12. jvm中的方法区变成了元数据区PermGen变成了Metaspace
  13. 更好的类型推测机制(不需要太多的强制类型转换了)
  14. Java 8 将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。

Exception和Error

Exception和Error都是继承于Throwable 类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。

Exception是java程序运行中可预料的异常情况,咱们可以获取到这种异常,并且对这种异常进行业务外的处理。

Error是java程序运行中不可预料的异常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值