1 Star 0 Fork 31

阿明 / Ebooks

forked from Java精选 / Ebooks 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
Java 并发面试题及答案整理2021年最新汇总版.md 12.97 KB
一键复制 编辑 原始数据 按行查看 历史

Java 并发面试题及答案整理2021年最新汇总版

全部面试题答案,更新日期:01月30日,直接下载吧!

下载链接:高清500+份面试题资料及电子书,累计 10000+ 页大厂面试题 PDF

Java 并发

题1:线程池都有哪些拒绝策略?

当线程池的任务缓存队列已满且线程池中的线程数目达到maximumPoolSize时,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务

ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

题2:锁优化的方法有哪些?

减少锁持有时间

减少其他线程等待的时间,只在有线程安全要求的程序代码上加锁。

减小锁粒度

将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。

锁分离

读写锁ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥。即保证了线程安全,又提高了性能。

读写分离思想可以延伸,只要操作互不影响,锁就可以分离。

锁粗化

为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早的获得资源执行任务。

锁消除

在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作。

题3:使用多线程可能带来什么问题?

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题。

比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。

题4:Java 中 volatile 关键字有什么作用?

Java语言提供了弱同步机制,即volatile变量,以确保变量的更新通知其他线程。

volatile变量具备变量可见性、禁止重排序两种特性。

volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

volatile变量的两种特性:

变量可见性

保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程是可以立即获取的。

禁止重排序

volatile禁止了指令重排。比sychronized更轻量级的同步锁。在访问volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

volatile适合场景:一个变量被多个线程共享,线程直接给这个变量赋值。

当对非volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同CPU cache中。而声明变量是volatile的,JVM 保证了每次读变量都从内存中读,跳过CPU cache这一步。

适用场景

值得说明的是对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证“i++”这种操作的原子性,因为本质上i++是读、写两次操作。在某些场景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。

总体来说,需要必须同时满足下面两个条件时才能保证并发环境的线程安全:

1)对变量的写操作不依赖于当前值(比如 “i++”),或者说是单纯的变量赋值(boolean flag = true)。

2)该变量没有包含在具有其他变量的不变式中,也就是说,不同的volatile变量之间,不 能互相依赖。只有在状态真正独立于程序内其他内容时才能使用volatile。

题5:常用的并发工具类有哪些?

CountDownLatch闭锁

CountDownLatch是一个同步计数器,初始化时传入需要计数线程的等待数,可能是等于或大于等待执行完的线程数。调用多个线程之间的同步或说起到线程之间的通信(不是互斥)一组线程等待其他线程完成工作后在执行,相当于加强的join。

CyclicBarrier栅栏

CyclicBarrier字面意思是栅栏,是多线程中重要的类,主要用于线程之间互相等待的问题,初始化时传入需要等待的线程数。

作用:让一组线程达到某个屏障被阻塞直到一组内最后一个线程达到屏蔽时,屏蔽开放,所有被阻塞的线程才会继续运行。

Semophore信号量

semaphore称为信号量是操作系统的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。

作用:semaphore管理一系列许可每个acquire()方法阻塞,直到有一个许可证可以获得,然后拿走许可证,每个release方法增加一个许可证,这可能会释放一个阻塞的acquire()方法,然而并没有实际的许可保证这个对象,semaphore只是维持了一个可获取许可的数量,主要控制同时访问某个特定资源的线程数量,多用在流量控制。

Exchanger交换器

Exchange类似于交换器可以在队中元素进行配对和交换线程的同步点,用于两个线程之间的交换。

具体来说,Exchanger类允许两个线程之间定义同步点,当两个线程达到同步点时,它们交换数据结构,因此第一个线程的数据结构进入到第二个线程当中,第二个线程的数据结构进入到第一个线程当中。

题6:Java 中 AQS 实现方式是什么?

AQS的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。

题7:Java 中创建线程池有哪些参数?

从java.util.concurrent.ThreadPoolExecutor源码中可以看出,线程池的构造函数有7个参数,分别是corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。

public ThreadPoolExecutor(int corePoolSize,
						  int maximumPoolSize,
						  long keepAliveTime,
						  TimeUnit unit,
						  BlockingQueue<Runnable> workQueue) {
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
		 Executors.defaultThreadFactory(), defaultHandler);
}

1、corePoolSize 线程池核心线程大小

线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。

2、maximumPoolSize 线程池最大线程数量

一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

3、keepAliveTime 空闲线程存活时间

一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

4、unit 空闲线程存活时间单位

keepAliveTime的计量单位

5、workQueue 工作队列

新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

1)ArrayBlockingQueue

基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

2)LinkedBlockingQuene

基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

3)SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

4)PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

6、threadFactory 线程工厂

创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

7、handler 拒绝策略

当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就会执行拒绝策略。

题8:什么是Executors框架?

Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。

无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executors框架可以非常方便的创建一个线程池。

题9:SynchronousQueue 队列的大小是多少?

实际上SynchronousQueue本身是没有容量的大小,所以也无法查看其容量的大小,其内部的size方法都是写死的返回0值。

题10:什么是FutureTask?

Java并发程序中FutureTask表示一个可以取消的异步运算。

FutureTask有启动和取消运算、查询运算是否完成和取回运算结果等方法。

只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。

一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

题11:thread-类中-start()-和-run()-方法有什么区别

题12:公平锁和非公平锁有什么区别

题13:什么是并发容器的实现

题14:什么是不可变对象对写并发应用有什么帮助

题15:如何检测一个线程是否拥有锁

题16:什么是守护线程

题17:什么是乐观锁什么是悲观锁

题18:为什么使用-executor-框架

题19:cas-有什么缺点

题20:java-中-aqs-底层原理是什么

题21:什么是-java-优先级队列priority-queue

题22:sleep()-方法和-wait()-方法有什么区别和共同点

题23:java-中-volatile-变量和-atomic-变量有什么不同

题24:什么是线程死锁

题25:多线程同步和互斥有几种实现方法

大厂面试题

大厂面试题

大厂面试题

Java
1
https://gitee.com/AminDev/ebooks.git
git@gitee.com:AminDev/ebooks.git
AminDev
ebooks
Ebooks
master

搜索帮助