1、什么是线程池?

线程池能够了解为一个具备多个线程的线程汇合.



2、应用线程池的益处

  • 升高资源耗费。通过反复利用已创立的线程升高线程创立和销毁造成的耗费。
  • 进步响应速度。当工作达到时,工作能够不须要的等到线程创立就能立刻执行。
  • 进步线程的可管理性。线程是稀缺资源,如果无限度的创立,不仅会耗费系统资源,还会升高零碎的稳定性,应用线程池能够进行对立的调配,调优和监控。



3、线程池的外围参数


corePoolSize 外围线程数,没达到外围线程数时,会创立新的线程。当达到外围线程数时,工作会进去队列

maximumPoolSize 最大线程数,能够为Integer.MAX_VALUE 21亿。当达到外围线程数且队列满了的时候,会去创立额定的线程来执行工作,最多不超过最大线程数

keepAliveTime 存活工夫,当工作解决实现,额定的线程存活一段时间后,会自行销毁。闲暇等待时间(该参数默认对外围线程有效,当allowCoreThreadTimeOut手动设置为true时,外围线程超过存活工夫后才会被销毁)

TimeUnit 闲暇等待时间的单位

BlockingQueue :工作进来,如果外围线程数满了,则工作进入队列中期待。

ThreadFactory 线程创立工厂

RejectExecutionHandler回绝策略,当最大线程数满了并且队列也满了的时候,如果再有工作进来就会启用回绝策略。


参考源码

    /**     * Creates a new {@code ThreadPoolExecutor} with the given initial     * parameters and default thread factory and rejected execution handler.     * It may be more convenient to use one of the {@link Executors} factory     * methods instead of this general purpose constructor.     *     * @param corePoolSize the number of threads to keep in the pool, even     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set     * @param maximumPoolSize the maximum number of threads to allow in the     *        pool     * @param keepAliveTime when the number of threads is greater than     *        the core, this is the maximum time that excess idle threads     *        will wait for new tasks before terminating.     * @param unit the time unit for the {@code keepAliveTime} argument     * @param workQueue the queue to use for holding tasks before they are     *        executed.  This queue will hold only the {@code Runnable}     *        tasks submitted by the {@code execute} method.     * @throws IllegalArgumentException if one of the following holds:<br>     *         {@code corePoolSize < 0}<br>     *         {@code keepAliveTime < 0}<br>     *         {@code maximumPoolSize <= 0}<br>     *         {@code maximumPoolSize < corePoolSize}     * @throws NullPointerException if {@code workQueue} is null     */    public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue) {        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,             Executors.defaultThreadFactory(), defaultHandler);    }



4、线程池的解决流程



5、线程池的创立形式有哪些?


通过Executors工具类创立指定线程池

通过 new ThreadPoolExecutor() 自定义线程池,传入指定参数



6、罕用线程池及它们的应用场景


newFixedThreadPool():固定线程数的线程池

public static ExecutorService newFixedThreadPool(int nThreads) {    return new ThreadPoolExecutor(nThreads, nThreads,                                  0L, TimeUnit.MILLISECONDS,                                  new LinkedBlockingQueue<Runnable>());}

线程池特点:

  • 外围线程数和最大线程数大小一样
  • 没有所谓的非闲暇工夫,即keepAliveTime为0
  • 阻塞队列为无界队列LinkedBlockingQueue

毛病

  • 如果某工作执行工夫过长,而导致大量工作沉积在阻塞队列中,或者说在某一时刻大量工作进来则会导致机器内存应用一直飙升,最终导致OOM

应用场景

newFixedThreadPool 实用于解决CPU密集型的工作,确保CPU在长期被工作线程应用的状况下,尽可能的少的调配线程,即实用执行长期的工作。


newCachedThreadPool()

public static ExecutorService newCachedThreadPool() {    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                  60L, TimeUnit.SECONDS,                                  new SynchronousQueue<Runnable>());}

线程池特点:

  • 外围线程数为0
  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是SynchronousQueue
  • 非核心线程闲暇存活工夫为60秒

毛病

  • 如果工作的提交速度大于线程解决工作的速度,那么就会一直地创立新线程极其状况下会耗尽CPU和内存资源
  • CachedThreadPool容许创立的线程数量为 Integer.MAX_VALUE ,可能会创立大量线程,从而导致 OOM。
工作队列采纳的是SynchronousQueue,这个队列是无奈插入工作的,一有工作立刻执行

应用场景

实用于并发执行大量短期的小工作。


newSingleThreadExecutor()

public static ExecutorService newSingleThreadExecutor() {    return new FinalizableDelegatedExecutorService        (new ThreadPoolExecutor(1, 1,                                0L, TimeUnit.MILLISECONDS,                                new LinkedBlockingQueue<Runnable>()));}

线程池特点

  • 外围线程数为1
  • 最大线程数也为1
  • 阻塞队列是LinkedBlockingQueue
  • keepAliveTime为0

毛病

  • LinkedBlockingQueue 为无界队列,可能会导致OOM

应用场景

  • 实用于串行执行工作的场景,一个工作一个工作地执行。

newScheduledThreadPool()

public ScheduledThreadPoolExecutor(int corePoolSize) {    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,          new DelayedWorkQueue());}

线程池特点

  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0
  • scheduleAtFixedRate() :按某种速率周期执行
  • scheduleWithFixedDelay():在某个提早后执行

应用场景

周期性执行工作的场景,须要限度线程数量的场景



7、线程池被创立后外面有线程吗?


线程池被创立后如果没有工作过去,是不会有线程的。



8、你晓得有什么办法对线程池进行预热吗?


==线程预热能够应用以下两个办法==

1.只启动一个线程预热

2.全副启动预热



9、线程池的状态有哪些?

参考源码

//记录线程池的状态,曾经线程池中线程的个数,初始化状态为 Runningprivate final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));//线程池的五种状态private static final int RUNNING    = -1 << COUNT_BITS;private static final int SHUTDOWN   =  0 << COUNT_BITS;private static final int STOP       =  1 << COUNT_BITS;private static final int TIDYING    =  2 << COUNT_BITS;private static final int TERMINATED =  3 << COUNT_BITS;

1、RUNNING

  1. 状态阐明:线程池处在RUNNING状态时,可能接管新工作,以及对已增加的工作进行解决。
  2. 状态切换线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创立,就处于RUNNING状态,并且线程池中的工作数为0!

2、ShutDown

  1. 状态阐明:线程池处在SHUTDOWN状态时,不接管新工作,但能解决已增加的工作。
  2. 状态切换:调用线程池的shutdown() 时,线程池由RUNNING -> SHUTDOWN。

3、STOP

  1. 状态阐明:线程池处在STOP状态时,不接管新工作,不解决已增加的工作,并且会中断正在解决的工作。
  2. 状态切换:调用线程池的shutdownNow() 时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、tidying

  1. 状态阐明当所有的工作已终止,ctl记录的”工作数量”为0,线程池会变为TIDYING状态。
    当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的解决;能够通过重载terminated()函数来实现。
  2. 状态切换当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的工作也为空时,就会由 SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的工作为空时,就会由STOP -> TIDYING。

5、 TERMINATED(terminated)

  1. 状态阐明:线程池彻底终止,就变成TERMINATED状态。
  2. 状态切换线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。



10、线程池的回绝策略有那些?

AbortPolicy(默认),间接抛出一个类型为 RejectedExecutionException 的 RuntimeException异样阻止零碎的失常运行。

DiscardPolicy间接抛弃工作,不给予任何解决也不抛出异样。如果容许工作失落的话,这是最好的计划。

DiscardOldestPolicy摈弃队列中等待时间最长的工作,而后把当前任务退出队列中尝试再次提交工作。

CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会摈弃工作也不会抛出异样,而是将某些工作回退到调用者,从而升高新工作的流量。



11、线程池的线程数到底怎么配置?

判断当前任务是CPU 密集型还是 IO 密集型

公式

  • CPU 密集型工作(N+1): 这种工作耗费的次要是 CPU 资源,能够将线程数设置为 N(CPU 外围数)+1,比 CPU 外围数多进去的一个线程是为了避免线程偶发的缺页中断,或者其它起因导致的工作暂停而带来的影响。 一旦工作暂停,CPU 就会处于闲暇状态,而在这种状况下多进去的一个线程就能够充分利用 CPU 的闲暇工夫。
  • I/O 密集型工作(2N): 这种工作利用起来,零碎会用大部分的工夫来解决 I/O 交互,而线程在解决 I/O 的时间段内不会占用 CPU 来解决,这时就能够将 CPU 交出给其它线程应用。因而在 I/O 密集型工作的利用中,咱们能够多配置一些线程,具体的计算方法是 2N。



12、execute 和 submit的区别

1. 办法起源不同

execut()是在线程池的顶级接口Executor中定义的,而且只有这一个接口,可见这个办法的重要性。

public interface Executor {    void execute(Runnable command);}

在ThreadPoolExecutor类中有它的具体实现。

submit()是在ExecutorService接口中定义的,并定义了三种重载形式,具体能够查看JDK文档

<T> Future<T> submit(Callable<T> task);Future<?> submit(Runnable task);<T> Future<T> submit(Runnable task, T result);

2. 承受参数不同

execute()办法只能接管实现Runnable接口类型的工作
submit()办法则既能够接管Runnable类型的工作,也能够接管Callable类型的工作。


3. 返回值不同

execute()的返回值是void,线程提交后不能失去线程的返回值。
submit()的返回值是Future,通过Future的get()办法能够获取到线程执行的返回值,get()办法是同步的,执行get()办法时,如果线程还没执行完,会同步期待,直到线程执行实现。

尽管submit()办法能够提交Runnable类型的参数,但执行Future办法的get()时,线程执行完会返回null,不会有理论的返回值,这是因为Runable原本就没有返回值

4. 对于异样解决不同

execute在执行工作时,如果遇到异样会间接抛出,

而submit不会间接抛出,只有在调用Future的get办法获取返回值时,才会抛出异样。



尾言

我是 Code皮皮虾,将来的日子里会不断更新出对大家无益的博文,期待大家的关注!!!

创作不易,如果这篇博文对各位有帮忙,心愿各位小伙伴能够==点赞和关注我哦==,感激反对,咱们下次再见~~~

==分享纲要==

大厂面试题专栏


Java从入门到入坟学习路线目录索引


开源爬虫实例教程目录索引

更多精彩内容分享,请点击 Hello World (●'◡'●)