多线程常见⾯试题及答案
1、如何在Java中实现线程(4种)?
1.继承Thread类,重写run⽅法(其实Thread类本⾝也实现了Runnable接⼝)
一个线程可以包含多个进程2.实现Runnable接⼝,重写run⽅法
3.实现Callable接⼝,重写call⽅法(有返回值)
4.使⽤线程池(有返回值)
2、在具体多线程编程实践中,如何选⽤Runnable还是Thread?
Java中实现多线程有两种⽅法:继承Thread类、实现Runnable接⼝,在程序开发中只要是多线程,肯定永远以实现Runnable接⼝为主,因为实现Runnable接⼝相⽐继承Thread类有如下优势:
1、可以避免由于Java的单继承特性⽽带来的局限;
2、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独⽴的;
适合多个相同程序代码的线程区处理同⼀资源的情况。
3、Thread类中的start()和run()⽅法有什么区别?
start()⽅法来启动线程,真正实现了多线程运⾏,这时⽆需等待run⽅法体代码执⾏完毕⽽直接继续执⾏下⾯的代码: 通过调⽤Thread 类的start()⽅法来启动⼀个线程,这时此线程是处于就绪状态,并没有运⾏。然后通过此Thread类调⽤⽅法run()来完成其运⾏操作的,这⾥⽅法run()称为线程体,它包含了要执⾏的这个线程的内容,Run⽅法运⾏结束,此线程终⽌,⽽CPU再运⾏其它线程。
run()⽅法当作普通⽅法的⽅式调⽤,程序还是要顺序执⾏,还是要等待run⽅法体执⾏完毕后才可继续执⾏下⾯的代码: ⽽如果直接⽤run⽅法,这只是调⽤⼀个⽅法⽽已,程序中依然只有主线程–这⼀个线程,其程序执⾏路径还是只有⼀条,这样就没有达到多线程的⽬的。
4、Java中Runnable和Callable有什么不同
相同点:
1. 两者都是接⼝;(废话)
2. 两者都可⽤来编写多线程程序;
3. 两者都需要调⽤Thread.start()启动线程;
不同点:
1. 两者最⼤的不同点是:实现Callable接⼝的任务线程能返回执⾏结果;⽽实现Runnable接⼝的任务线程不能返回结果;
2. Callable接⼝的call()⽅法允许抛出异常;⽽Runnable接⼝的run()⽅法的异常只能在内部消化,不能继续上抛;
注意点:
Callable接⼝⽀持返回执⾏结果,此时需要调⽤()⽅法实现,此⽅法会阻塞主线程直到获取‘将来’结果;当不调⽤此⽅法时,主线程不会阻塞!
5、如何避免死锁?
1. 加锁顺序
按照顺序加锁是⼀种有效的死锁预防机制。但是,这种⽅式需要你事先知道所有可能会⽤到的锁(并对这些锁做适当的排序),但总有些时候是⽆法预知的。
2. 加锁时限
另外⼀个可以避免死锁的⽅法是在尝试获取锁的时候加⼀个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。
3.死锁检测
死锁检测是⼀个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可⾏的场景。
每当⼀个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。
当⼀个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发⽣。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查⼀下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发⽣了死锁(线程A拥有锁1,请求锁7;线程B拥有锁7,请求锁1)。
当然,死锁⼀般要⽐两个线程互相持有对⽅的锁这种情况要复杂的多。线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D⼜在等待线程A。线程A为了检测死锁,它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A到了线程C,然后⼜到了线程D,发现线程D请求的锁被线程A⾃⼰持有着。这是它就知道发⽣了死锁。
6、Java多线程中调⽤wait() 和 sleep()⽅法有什么不同?
共同点:
1. 他们都是在多线程的环境下,都可以在程序的调⽤处阻塞指定的毫秒数,并返回。
2. wait()和sleep()都可以通过interrupt()⽅法 打断线程的暂停状态 ,从⽽使线程⽴刻抛出InterruptedException。
如果线程A希望⽴即结束线程B,则可以对线程B对应的Thread实例调⽤interrupt⽅法。如果此刻线程B正在wait/sleep /join,则线程B 会⽴刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程⾃⼰从内部抛出的,并不是interrupt()⽅法抛出的。对某⼀线程调⽤ interrupt()时,如果该线程正在执⾏普通的代码,那么该线程根本就不会抛出InterruptedException。但是,⼀旦该线程进⼊到 wait()/sleep()/join()后,就会⽴刻抛出InterruptedException 。
不同点:
1. Thread类的⽅法:sleep(),yield()等
Object的⽅法:wait()和notify()等
2. 每个对象都有⼀个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
sleep⽅法没有释放锁,⽽wait⽅法释放了锁,使得其他线程可以使⽤同步控制块或者⽅法。
3. wait,notify和notifyAll只能在同步控制⽅法或者同步控制块⾥⾯使⽤,⽽sleep可以在任何地⽅使⽤
4. sleep必须捕获异常,⽽wait,notify和notifyAll不需要捕获异常
7、什么是Executor框架
我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重⽤,降低资源消耗,提⾼响应速度等。线程⽤于执⾏异步任务,单个的线程既是⼯作单元也是执⾏机制,从JDK1.5开始,为了把⼯作单元与执⾏机制分离开,Executor框架诞⽣了,他是⼀个⽤于统⼀创建与运⾏的接⼝。Executor框架实现的就是线程池的功能。
8、在Java中Executor和Executors的区别
Executors是⼀个类, Executors类提供了若⼲个静态⽅法,⽤于⽣成不同类型的线程池:
9、什么是多线程中的上下⽂切换?
即使是单核CPU也⽀持多线程执⾏代码,CPU通过给每个线程分配CPU时间⽚来实现这个机制。时间⽚是CPU分配给各个线程的时间,因为时间⽚⾮常短,所以CPU通过不停地切换线程执⾏,让我们感觉多个线程时同时执⾏的,时间⽚⼀般是⼏⼗毫秒(ms)。
CPU通过时间⽚分配算法来循环执⾏任务,当前任务执⾏⼀个时间⽚后会切换到下⼀个任务。但是,在切换前会保存上⼀个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是⼀次上下⽂切换。
这就像我们同时读两本书,当我们在读⼀本英⽂的技术书籍时,发现某
10、什么是线程安全
线程安全的代码是多个线程同时执⾏也能⼯作的代码
如果⼀段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的
如果你的代码所在的进程中有多个线程在同时运⾏,⽽这些线程可能会同时运⾏这段代码。如果每次运⾏结果和单线程运⾏的结果是⼀样的,⽽且其他的变量的值也和预期的是⼀样的,
就是线程安全的。
或者说:⼀个类或者程序所提供的接⼝对于线程来说是原⼦操作或者多个线程之间的切换不会导致该接⼝的执⾏结果存在⼆义性,也就是说我们不⽤考虑同步的问题。
11、如何检测死锁?怎么预防死锁?
利⽤jstack 命令检测死锁 jpg jstack -l pid &
1. 破坏“不可剥夺”条件:⼀个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加⼊到 系统的资源列表中,可以被其他的进程使⽤,⽽等待的进程只有重新获得⾃⼰原有的资源以及新申请的资源才可以重新启动,执⾏。
2. 破坏”请求与保持条件“:第⼀种⽅法静态分配即每个进程在开始执⾏时就申请他所需要的全部资源。第⼆种是动态分配即每个进程在申请所需要的资源时他本⾝不占⽤系统资源。
3. 破坏“循环等待”条件:采⽤资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采⽤较⼤的编号,在申请资源时必须按照编号的顺序进⾏,⼀个进程只有获得较⼩编号的进程才能申请较⼤编号的进程。
12、Java中⽤到的线程调度算法是什么
抢占式。⼀个线程⽤完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出⼀个总的优先级并分配下⼀个时间⽚给某个线程执⾏
(时间⽚轮转法、优先级调度法、多级反馈队列调度法等
13、Java中如何获取到线程dump⽂件
jmap -dump:format=b,file=F:/heamdump.out 16540”命令即可以⽣成
jpg jstack -l pid &
14、池技术有什么作⽤,常见的池技术有哪些
起到对象复⽤
15、⽤线程池有什么好处,请谈谈线程池的使⽤场景
1、避免重复创建线程,减少在创建和 销毁线程时所花时间,及系统的整体开销
2、避免系统创建⼤量线程⽽消耗系统资源
3、⽤户提交的数据能够及时得到处理,响应速度快
4、能够更好的监控和管理线程
常量池 线程池 数据库连接池
16、线程池的技术原理是什么
预先启动⼀些线程,线程⽆限循环从任务队列中获取⼀个任务进⾏执⾏,直到线程池被关闭。如果某个线程因为执⾏某个任务发⽣异常⽽终⽌,那么重新创建⼀个新的线程⽽已。如此反复。
17、线程池有哪些种类,各⾃的使⽤场景是什么?
newCachedThreadPool:
底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插⼊到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻可⽤线程来执⾏,若有可以线程则执⾏,若没有可⽤线程则创建⼀个线程来执⾏该任务;若池中线程空闲时间超过指定⼤⼩,则该线程会被销毁。
适⽤:执⾏很多短期异步的⼩程序或者负载较轻的服务器
newFixedThreadPool:
底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new
LinkedBlockingQueue<Runnable>() ⽆解阻塞队列
通俗:创建可容纳固定数量线程的池⼦,每隔线程的存活时间是⽆限的,当池⼦满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进⼊阻塞队列中(⽆界的阻塞队列)
适⽤:执⾏长期的任务,性能好很多
newSingleThreadExecutor:
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue<Runnable>() ⽆解阻塞队列
通俗:创建只有⼀个线程的线程池,且线程的存活时间是⽆限的;当该线程正繁忙时,对于新任务会进⼊阻塞队列中(⽆界的阻塞队列)
适⽤:⼀个任务⼀个任务执⾏的场景
NewScheduledThreadPool:
底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() ⼀个按超时时间升序排序的队列
通俗:创建⼀个固定⼤⼩的线程池,线程池内线程存活时间⽆限制,线程池可以⽀持定时及周期性任务执⾏,如果所有线程均处于繁忙状态,对于新任务会进⼊DelayedWorkQueue队列中,这是⼀种按照超时时间排序的队列结构
适⽤:周期性执⾏任务的场景
18、线程池有哪些重要的参数?
a.核⼼线程数
b 最⼤线程数
c 线程空闲时间
c 时间单位
d 阻塞队列⼤⼩:queueCapacity
e 任务拒绝处理器 :rejectedExceptionHandler
19、你们在具体的设计开发过程中是如何设置这些重要参数的?
根据任务的特性具体⽅案 具体定制 参见17
20、单例的使⽤场景是什么,如何实现单例
系统中只存在⼀个实⼒,⼀种是枚举,还有⼀种私有静态内部类
单例对象的类必须保证只有⼀个实例存在。许多时候整个系统只需要拥有⼀个的全局对象,这样有利于我们协调系统整体的⾏为
21、如何在Java中创建线程安全的Singleton
public class StaticSingleton {
02 private StaticSingleton(){
03 System.out.println("StaticSingleton is create");
04 }
05 private static class SingletonHolder {
06 private static StaticSingleton instance = new StaticSingleton();
07 }
08 public static StaticSingleton getInstance() {
09 return SingletonHolder.instance;
10 }
22、11 }
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论