Java并发编程八股

本文整理了牛客上一个很棒的Java并发编程的八股,供童鞋们参考学习~

ConcurrentHashMap

什么是ConcurrentHashMap?相比于HashMap和HashTable的优势?

ConcurrentHashMap可以看成是线程安全且高效的HashMap,它比HashMap更安全,比HashTable更高效。

Java中的ConcurrentHashMap是如何实现的?

  • JDK1.7
    在JDK1.7中,ConcurrentHashMap是由segment分段锁数组构成,每个segment数组由多个HashEntry链表数组构成,因此ConcurrentHashMap定位一个元素需要两次hash,第一次hash的目的是定位到segment,第二次hash的目的是定位到链表的头部。
    在这里插入图片描述
  • JDK1.8
    JDK1.8之后的ConcurrentHashMap结构与HashMap相似,数组+链表/红黑树,当链表节点个数大于8时,链表会转换为红黑树。
    在这里插入图片描述
    从上图可以看出,JDK1.8的实现方式可以降低锁粒度。

ConcurrentHashMap结构变量中使用volatile和final修饰有什么作用?

final修饰变量可以保证变量不需要同步就可以被访问和共享,volatile可以保证内存的可见性,同时配合CAS操作可以在不加锁的情况下实现并发。

ConcurrentHashMap默认初始容量,扩容机制?

默认初始容量为16,之后每次扩容为之前的2倍。

ConcurrentHashMap的key和value是否可以为null?HashMap呢?

  • HashMap的key和value都可以为null,但key作为null只能有一个,value作为null可以有多个;
  • ConcurrentHashMap的key和value不能为null,否则会出现空指针异常(HashTable也是)。
    原因:
    ConcurrentHashMap是在多线程场景下使用的,如果ConcurrentHashMap.get(key)的值为null,无法辨别是key对应的value值为null,还是根本不存在这样的key值;而在单线程下使用的HashMap,可以使用containsKey(key)来判断到底是不存在key还是key对应的value值是null。

ConcurrentHashMap在JDK1.7和JDK1.8版本中的区别?

  • 结构上的不同
    JDK1.7是segment分段锁数组,JDK1.8是数组+链表/红黑树。
  • 保证线程安全方面
    JDK1.7采用分段锁机制,当一个线程占用锁时,一部分数据,提高了并发访问率;
    JDK1.8利用synchronized和CAS的方式保证线程安全。
  • 存取数据方面
  1. JDK1.7中的put()方法
    (1)先计算key对应的hash值,利用hash值对segment数组取余得到对应的segment对象;
    (2)尝试获取锁,如果失败则自旋直至成功,获取到锁后,利用hash值对HashEntry数组进行取余得到对应的entry对象;
    (3)遍历链表,查找对应的key值,如果找到则将旧的value值覆盖;否则添加到链表中。(1.7是头插,1.8是尾插)
  2. JDK1.8中的put()方法
    (1)计算key对应的hash值,利用hash值找到对应的数组下标,如果当前位置为空就直接写入数据;
    (2)利用CAS尝试写入,如果失败则自旋直至成功,如果都不满足,则利用synchronized锁写入数据。

ConcurrentHashMap迭代器的弱一致性

与HashMap不同的是,ConcurrentHashMap迭代器是弱一致性。

当ConcurrentHashMap的迭代器创建以后,在遍历hash表元素的过程中,hash表的中的元素可能会发生变化,如果这部分变化发生在已经遍历过的地方,迭代器不会反映出来,如果这部分变化发生在未遍历的地方,迭代器则会反映出来。换种说法就是put()方法将一个元素插入到底层数据结构后,get()可能在某段时间内害看不到这个元素。

这样的设计主要是为了ConcurrentHashMap的性能考虑,如果想做到强一致性,需要到处加锁,这样性能会下降很多。

ThreadLocal

什么是ThreadLocal?有哪些应用场景?

ThreadLocal是JDK java.lang包下的一个类,ThreadLocal为变量在每个线程中都创建一个副本,这样每个线程可以访问自己内部的副本变量,而不会和其它线程的局部变量冲突,实现了线程间的数据隔离。
ThreadLocal的应用场景:

  • 保存线程上下文信息,在需要的地方可以获取
  • 实现线程间的数据隔离
  • 数据库连接

ThreadLocal原理和内存泄漏

在这里插入图片描述
每个线程都有一个ThreadLocalMap,而ThreadLocalMap中保存着所有的ThreadLocal,其中ThreadLocal为ThreadLocalMap中的key。

  • 为什么ThreadLocal会发生内存泄漏呢?
    因为,ThreadLocal中的key是弱引用,而value是强引用。在进行垃圾回收时,key会被清理掉,而value不会被清理掉,这时如果不做任何处理,value将永远不会被回收,产生内存泄漏。
  • 如何解决ThreadLocal的内存泄漏?
    在使用完ThreadLocal后,调用remove()方法手动清理掉key为null的记录

线程池

什么是线程池以及为何要使用线程池?

线程池是一种实现多线程的方式。在线程池中提前创建好多个线程,使用时直接获取,使用完放回池中。

  • 为什么要使用线程池?
  1. 降低资源消耗:通过重复利用已经创建的线程,避免了创建、销毁线程带来的开销;
  2. 提高响应速度:当任务到达时,不需要创建线程,可以立马执行;
  3. 提高线程的可管理性:由线程池统一分配调度。

创建线程池的几种方法

  1. Executors工厂方法提供了以下几个常见的静态的工厂方法:
newSingleThreadExecutor:创建一个单线程的线程池
newFixedThreadPool:创建固定大小的线程池
newCachedThreadPool:创建可缓存的线程池
newScheduledThreadPool:创建一个无限大小的线程池
  1. new ThreadPoolExecutor方法

ThreadPoolExecutor构造函数的重要参数解析

三个重要参数:

corePoolSize:核心线程数,定义了最小可以同时运行的线程数量
maximumPoolSize:线程中允许存在的最大工作线程数量
workQueue:阻塞队列。新来的任务会先判断当前运行的线程数是否达到了核心线程数,如果达到的话,任务就会先放到阻塞队列。

线程池的执行流程

在这里插入图片描述

ThreadPoolExecutor的饱和策略(拒绝策略)

当阻塞队列已满并且同时运行的线程数量达到了线程池的容量时,就会执行饱和策略,主要有以下四种类型:

  • AbortPolicy策略:直接抛出异常拒绝新任务
  • CallerRunsPolicy策略:当线程池无法处理当前任务时,会将该任务交由提交任务的线程来执行
  • DiscardPolicy策略:直接丢弃新任务
  • DiscardOlddestPolicy:丢弃最早的未处理的任务请求

execute()方法和submit()方法的区别

  • execute()方法只能执行Runnable类型的任务;submit()方法可以执行Runnable和Callable类型的任务。
  • submit()方法可以返回持有计算结果的Future对象,同时可以跑出异常;execute()方法不可以。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值