Java 多线程相关的面试题收录总结

注: 史上最全Java多线程面试题及答案

1. 进程和线程的区别是什么?

1)进程是操作系统分配资源的最小单位;线程是cpu调度的最小单位。
2)进程与进程之间的资源是独享的;线程之间的资源是共享的。
3)操作系统中可以有多个进程(一个程序就是一个进程),一个进程包含多个线程。

2. 线程的生命周期及五种基本状态。在这里插入图片描述

1)新建状态(new):当线程对象创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法,线程就会进入就绪状态。就绪状态也说明该线程已经做好准备,随时等待CPU调度执行。
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入运行状态。就绪状态是进入运行状态唯一入口。
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂停放弃了对CPU的使用权,停止了执行,此时进入阻塞状态,直到进入就绪状态。阻塞状态可分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到阻塞状态。
同步阻塞:线程在获取synchronized同步锁失败时,它会进入同步阻塞状态。
其他阻塞:通过调用线程的sleep()或者join()或发出I/O请求时,线程会进入到阻塞状态,当sleep状态超时,join等待线程结束,I/O处理完毕,就会进入就绪状态。
5)死亡状态(Dead):线程执行完毕或者因为异常退出了run方法,该线程结束自己的生命周期。

3. 一个多线程死锁案例,如何避免及解决死锁问题?

死锁:线程A和线程B相互等待对方持有的锁,导致的无限等待现象
造成死锁的步骤:
1)两个线程里面分别持有两个Object对象锁:lock1和lock2.两个锁作为同步代码块的锁;
2)线程A的run()方法中的同步代码块,先获取lock1锁,之后再Thread.sleep(n)睡眠一会,然后再接着获取lock2.
3)线程B的run()方法中的同步代码块,先获取lock2锁,之后再Thread.sleep(n)睡眠一会,然后再接着获取lock1.
此时,线程A在睡眠醒后,持有lock1来获取lock2,但是此时,线程B在持有lock2去获取lock1.这样双方各持有对方所需要的锁,此时就形成了一个死锁。
在这里插入图片描述
如何避免死锁:
1)按顺序加锁
上面例子线程间加锁的顺序各不一致,导致死锁,如果每个线程都按同一个的加锁顺序来加锁,这样就不会出现死锁。
2)获取锁时间限制
每个获取锁的时候加上超时限制,如果超时就放弃锁的获取。
3)死锁检测
按线程间获取锁的关系检测线程间是否发生死锁,如果发生死锁就执行一定的策略,如终断线程或回滚操作等。

4. Wait和notify的原理?

调用wait方法,首先会获取监视器锁,获取成功后,会让当前线程进入等待状态并进入等待队列,最后释放持有的锁。
当其他线程调用notify方法,会选择从等待队列中唤醒任意一个线程,而执行完notify方法后,并不会立马唤醒该线程,原因是当前线程仍然持有这把锁,处于等待状态的线程无法获得锁,必须等到当前线程执行完monitorexit指令后,也就是锁被释放后,处于等待队列中的某个线程就获取到了锁。

5. Join()

Join方法只有在继承了Thread之后才能使用。
Join只有在调用start方法之后才能起作用。
当线程A调用了线程B的join方法时,线程A就是阻塞住,直到线程B执行完毕,A线程才会继续执行。

6. 什么是线程安全?保证线程安全三要素?

当多个线程同时访问一个对象时,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。
简单点说:就是在单线程环境执行的结果和多线程环境下执行的结果永远是一样的,无论执行多少次。
三要素:可见性、原子性、禁止指令重排。

7. volatile是线程安全的吗?

volatile不是线程安全的。
线程安全三要素:原子性,可见性,顺序性。
volatile特点:可见性,不保证原子性,禁止指令重排(顺序性)

8. volatile实现原理

1)在 JVM 底层 volatile 是采用“内存屏障”来实现的(顺序性)
2)缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个 CPU 在写数据时,如果发现操作的变量是共享变量,则会通知其他 CPU 告知该变量的缓存行是无效的,因此其他 CPU 在读取该变量时,发现其无效会重新从主存中加载数据。(可见性)
由于编译器和处理器都能执行指令重排优化,如果在指令间插入一条内存屏障则会告诉cpu,不管什么指令都不能和这条内存屏障指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的执行重排序优化;内存屏障还有就是强制刷出各种cpu的缓存数据。
对于Volatile变量写操作时:会在写操作后假如一条store屏障指令,讲工作内存中的共享变量值刷新回祝内存。
对于Volatile变量读操作时:会在读操作前假如一条load屏障指令,从主内存中读取共享变量的值。

9. Java有几种线程池

1)NewCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
适用:执行很多短期的任务
在这里插入图片描述
2)NewFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
适用:执行长期运行任务
在这里插入图片描述
3)NewSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO、LIFO、优先级)执行。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
适用:一个任务一个任务的执行场景
在这里插入图片描述
4)NewScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
适用:周期性执行任务的场景
在这里插入图片描述在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值