互联网面试第三讲聊聊线程池

1.什么是线程池?

线程池的基本思想是一种对象池,在程序启动时就开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

2.为什么要使用线程池?

Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead,and they provide a means of bounding and managing the resources,including threads, consumed when executing a collection of tasks.Each {@code ThreadPoolExecutor} also maintains some basic statistics, such as the number of completed tasks. 翻译下来就是线程池解决了2个问题: 在执行大量异步任务时,减少频繁创建资源的开销,可以提高性能 可以对资源进行管理和限制,避免无休止的创建线程,对系统带来压力

3.Java中常见线程池有哪些?

newFixedThreadPool 创建固定数目线程的线程池,当有一个新的任务提交时,线程池中若有空闲线程,则立即执行,若没有,则新的任务会被暂存在一个任务队列(默认无界队列int最大数)中,待有线程空闲时,便处理在任务队列中的任务。

newCachedThreadPool  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); 创建一个可缓存的线程池,若有空闲线程可以复用,则会优先使用可复用的线程,如果所有线程均在工作,此时有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。因为corePoolSize为0,因此空闲线程会在指定时间内(60秒)被回收。

newSingleThreadExecutor 创建一个单线程化的Executor,只会用唯一的工作线程来执行任务,包含一个无界队列,若多余一个任务被提交到该线程池,任务会被保存在一个任务队列(默认无界队列 int 最大数)中,待线程空闲,按先入先出的顺序执行队列中的任务

newScheduledThreadPool(int corePoolSize):定时线程池 创建一个支持定时及周期性任务执行的定时线程池.

4.线程池核心参数讲解

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) corePoolSize(核心线程数):当提交一个任务到线程池时,如果当前 poolSize < corePoolSize 时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程,这样可以减少任务被线程池处理时所需的等待时间。 maximumPoolSize(最大线程数):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。 keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。 TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。 workQueue(任务队列):用于保存等待执行的任务的阻塞队列,它相当于生产者-消费者模式中的传输通道。 threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。 RejectExecutionHandler(饱和策略):队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

5.线程池的执行原理

 

6.线程池中的常用队列类图 

7.线程池常用队列讲解

队列

特点

PriorityBlockingQueue

是一个无界优先级队列是基于优先级堆的。优先级队列种的元素根据自然顺序进行排序,或者通过在队列构建时提供的Comparator进行排序,当然这取决于使用哪种构造函数。优先级队列不允许空(null)元素。一个依赖自然顺序的优先级队列也不允许插入不可比较的对象。

阻塞

无界

SynchronousQueue

SynchronousQueue是线程安全的。SynchronousQueue和其他的BlockingQueue不同的是SynchronousQueue的capacity是0。即SynchronousQueue不存储任何元素。

也就是说SynchronousQueue的每一次insert操作,必须等待其他线性的remove操作。而每一个remove操作也必须等待其他线程的insert操作

阻塞

有界

DelayQueue

实现延迟获取元素的无界队列无界阻塞队列,其中添加进该队列的元素必须实现Delayed接口(指定延迟时间),而且只有在延迟期满后才能从中提取元素。

阻塞

无界

LinkedBlockingQueue

是一个基于链表实现的可选边界的阻塞队列。不给大小默认是Integer.Max 无界队列

阻塞

无界

ArrayBlockingQueue

是用数组实现的有界阻塞队列。这种队列中的元素按FIFO(先进先出)排序。队头是在队列中停留最长时间的元素。队尾是在队列中停留时间最短的元素。新元素插入到队列的尾部,并且队列检索操作在队列的头部获取元素。

阻塞

有界

LinkedTransferQueue

一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列,LinkedTransferQueue多了tryTransfer和transfer方法

阻塞

无界

LinkedBlockingDeque

由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争

阻塞

无界

 8.线程池中的状态机

程池的初始化状态是RUNNING,能够接收新任务,以及对已添加的任务进行处理。 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。  调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。 调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。 当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。 当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。 线程池彻底终止,就变成TERMINATED状态。线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

9. 线程池中的拒绝策略

AbortPolicy:丢弃任务并抛出RejectedExecutionException异常; DiscardPolicy:丢弃任务,但是不抛出异常; DisCardOldSetPolicy:丢弃队列最前面的任务,然后提交新来的任务; CallerRunPolicy:由调用线程(提交任务的线程,主线程)处理该任务

10.线程池2中提交方式有啥区别

submit()方法返回一个future,那么我们可以通过这个future来判断任务是否执行成功,通过future的get方法来获取返回值 try {      Object s = future.get();    } catch (InterruptedException e) {    // 处理中断异常    } catch (ExecutionException e) {    // 处理无法执行任务异常    } finally {    // 关闭线程池    executor.shutdown(); }

execute()方法没有返回值,所以无法判断任务知否被线程池执行成功 threadsPool.execute(new Runnable() {     @Override     public void run() {     // TODO Auto-generated method stub    } });

11.如何设置线程池大小

CPU 密集型任务:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。 IO 密集型任务:比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。在知道如何判断任务的类别后,让我们分两个场景进行讨论: 1)CPU密集性任务线程分配原则:理论上线程的数量 = CPU 核数就是最合适的,不过通常把线程的数量设置为CPU 核数 +1,会实现最优的利用率。 2)对应IO密集性任务分配原则: a:对于 IO 密集型计算场景,最佳的线程数是与程序中 CPU 计算和 IO 操作的耗时比相关的,《Java并发编程实战》的作者 Brain Goetz 推荐的计算方法如下:  线程数 = CPU 核心数 * (1 + IO 耗时/ CPU 耗时) b:还有一派的计算方式是《Java虚拟机并发编程》中提出的: 线程数 = CPU 核心数 / (1 - 阻塞系数) 其中计算密集型阻塞系数为 0,IO 密集型阻塞系数接近 1,一般认为在 0.8 ~ 0.9 之间。比如 2 核 CPU,按照公式就是 2 / ( 1 - 0.9 ) = 20 个线程数

12.正确创建线程池的姿势

 ThreadFactory threadFactory=new ThreadFactoryBuilder()                .setDaemon(false)                .setNameFormat("-log-thread%")                .build();         ThreadPoolExecutor threadPoolExecutor=new                 ThreadPoolExecutor(5,5,0L,                 TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),threadFactory,                 new ThreadPoolExecutor.CallerRunsPolicy());

13.非核心线程延迟死亡,如何实现?

通过阻塞队列poll(),让线程阻塞等待一段时间,如果没有取到任务,则线程死亡

14.如何释放核心线程?

线程退出(回收)有两种方法: 1、参数allowCoreThreadTimeOut为true,等待keepAliveTime后,线程就会被回收。 2、加上executor.shutdown();马上将线程池中的空闲线程回收。该方法会使得keepAliveTime参数失效

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值