乐趣区

关于面试问题:见招拆招老油条教你如何化解大厂面试官的线程池夺命连环炮


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、线程池的状态有哪些?

参考源码

// 记录线程池的状态,曾经线程池中线程的个数,初始化状态为 Running
private 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 (●’◡’●)


退出移动版