常见面试题(多线程)

目录

1、实现多线程的几种方式?

2、创建线程的三种方式的对比?

3、为什么使用线程池,线程池的优势是什么?

5、线程池有几种创建方式?

6、线程池中的参数有哪些?

7、线程池的工作队列有哪些?

8、线程池的工作原理?

9、线程池的拒绝策略有哪几种?

10、线程池生产实际用的哪种?

11、线程池的手写改造如何改造?

12、线程池如何配置合理线程数?

13、线程池比较重要的几个类?


1、实现多线程的几种方式?

  • 继承thread类
  • 实现Runnable接口
  • 实现callable接口,使用Future Task类来包装callable对象,(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)
  • 线程池

2、创建线程的三种方式的对比?

1、采用实现Runnable、Callable接口的方式创建多线程

      优势:

       线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

       在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

       劣势:

     编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

2、使用继承Thread类的方式创建多线程

      优势:

      编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

      劣势:

      线程类已经继承了Thread类,所以不能再继承其他父类。

3、Runnable和Callable的区别

     (1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。

     (2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

     (3) call方法可以抛出异常,run方法不可以。

     (4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的

完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果future.get()。

3、为什么使用线程池,线程池的优势是什么?

线程池的主要工作是控制运行线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

主要特点为:线程复用;控制最大并发数;管理线程。

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高相应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的调配、调优和监控。
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

5、线程池有几种创建方式?

newSingleThreadExecutor 创建一个单线程化的线程池,它只会唯一工作线程来执行任务,确保所有任务按照指定顺序(FIFO,LIFO,优先级)执行。(一个任务执行的场景)

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。(执行长期的任务,性能好很多)

newCachedThreadPool 创建一个可缓存的线程池,如果线程池长度超过处理需求,可灵活回收空闲线程,若无可回收,则新建线程。(适用于执行很多短期异步的小程序或者负载较轻的服务器)

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。(思该丢)

6、线程池中的参数有哪些?

  1. corePoolSize:线程中的常驻核心线程数
  2. maxmumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
  3. keepAliveTime:空闲线程存活时间;当空闲线程超过这个时间,会销毁回收
  4. unit:keepAliveTime的单位
  5. workQueue:工作队列,被提交但尚未被执行的任务。新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
  6. threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
  7. handler:拒绝策略,表示当队列满了并且工作线程大于或等于线程池的最大线程数(maxmumPoolSize)

7、线程池的工作队列有哪些?

①ArrayBlockingQueue

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

②LinkedBlockingQuene

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

③SynchronousQuene

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

④PriorityBlockingQueue

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

8、线程池的工作原理?

9、线程池的拒绝策略有哪几种?

AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。

DiscardPolicy:丢弃任务,不予任何处理,也不抛出异常。如果允许任务丢失,这是最好的一种方案

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务

CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务(调用者运行一种调节机制,该策略既不会抛弃任务,也不会抛弃异常,而是将某些任务回退到调用者,从而降低新任务的流量)

都是使用ThreadPoolExecutor类

10、线程池生产实际用的哪种?

实际一个都不用,不允许使用Executors去创建,而是使用ThreadPoolExecutor的方式

FixedThreadPool和SingleThreadPool:允许的请求队列的长度Integer.MAX_VALUE,可能会堆积大量的请求,从而导致oom。

CachedThreadPool 和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致oom。

11、线程池的手写改造如何改造?

ExecutorService threadPool = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, 
				new LinkedBlockingQueue<Runnable>(3),Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());

12、线程池如何配置合理线程数?

cpu密集型,

该任务需要大量的运算,没有阻塞,cpu一直全速运行。密集任务只有在真正的多核cpu上才能得到加速。要配置尽可能少的线程数量:一般公式为: cpu核数 + 1个线程的线程池

io密集型(仅供参考)

1、io密集型任务线程数并不是一直在执行,则应配置尽可能多的线程,如CPU核数* 2

2.、io密集型,即该任务需要大量的io,即大量的阻塞。在单线程上运行io密集型的任务会导致浪费大量的cpu运算能力,浪费在等待。

参考公式: CPU核数/1-阻塞系数   阻塞系数在0.8-0.9之间,  比如8核Cpu:8/1 -0.9 = 80个线程数

 

System.out.println(Runtime.getRuntime().availableProcessors());  获取实际的线程数

13、线程池比较重要的几个类?

ExecutorService:真正的线程池接口

ThreadPoolExecutor: ExecutorService的默认实现,底层实现方法。

ScheduledExecutorService:能和Timer/Timer Task类似,解决那些需要任务重复执行的问题。

ScheduledThreadPoolExecutor: 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
 

参考资料:

https://blog.csdn.net/qq_35275233/article/details/87893337   (java 创建线程的三种方式、创建线程池的四种方式)

https://blog.csdn.net/suifeng629/article/details/98884972  (四种线程池拒绝策略)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值