Java线程池的灵魂拷问,这些你都会吗?

问题1:为什么要使用线程池?

传统的创建线程方式 new Thread(new Runnable(){}).start(); 殊不知这种创建线程有以下缺点:

  1. 如果频繁创建和销毁线程会带来性能损耗,线程的创建和销毁都需要时间,如果创建和销毁的时间大于任务执行时间,反而得不偿失。
  2. 线程需要占用内存空间,大量的线程会抢占宝贵的内存资源,会导致oom,大量的线程也会给gc很大压力,延长gc的停顿时间。
  3. 大量的线程会抢占cpu资源,cpu在不停的做上下文切换,反而没有时间去处理线程运行的时候该处理的任务。
问题2:线程池有哪些好处?
  1. 降低资源消耗:通过池化技术进行线程复用,降低线程频繁创建和销毁带来的损耗。
  2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
问题3:了解JDK Executors线程池吗?知道JDK提供了哪些默认的实现吗?

SingleThreadPool、FixedThreadPool、CachedThreadPool、ScheduledThreadPool

问题4:看过阿里巴巴java开发手册吗?知道为啥不允许使用默认的实现吗?

建议通过new ThreadPoolExecutor()方式手动创建线程池,使我们更加明确线程池的运行规则,规避资源耗尽的风险。同时JDK默认实现的线程池实现有以下缺点:

  1. SingleThreadPool 和 FixedThreadPool 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,导致OOM
  2. CachedThreadPool 和 ScheduledThreadPool 允许创建的线程数量为Integer.MAX_VALUE 可能会创建大量线程,导致OOM
问题5:自定义线程池的几个常用参数?
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:空闲线程存活时间
  • unit:时间单位
  • workQueue:存放任务的缓冲队列
  • threadFactory:创建线程的工厂
  • handler:拒绝策略
问题6:自定义线程池的常用参数值如何得来?

根据任务类型,IO密集型还是计算密集型

问题7:线程池的执行流程是怎样的?

所有任务的调度都是由execute方法完成的,这部分完成的工作是:检查现在线程池的运行状态、运行线程数、运行策略,决定接下来执行的流程,是直接申请线程执行,或是缓冲到队列中执行,亦或是直接拒绝该任务。其执行过程如下:

  1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
  2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
  3. 如果workerCount >=corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
  4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
    其执行流程如下图所示:
    execute
问题8:线程池的复用机制是怎样的?

工作线程通过getTask不断的获取任务,如果获取到任务,那么直接执行当前任务,否则阻塞等待任务的到来,如果超过一定的时间(空闲时间),还没有获取到任务,则线程被销毁。

问题9:了解线程池的状态吗?

ThreadPoolExecutor的运行状态有5种,分别为:
线程池状态
其生命周期转换如下入所示:
lifecycle

问题10:线程池的阻塞队列有哪些?

线程池中是以生产者消费者模式,通过一个阻塞队列来实现的。阻塞队列缓存任务,工作线程从阻塞队列中获取任务。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
阻塞队列

问题11:线程池的拒绝策略有哪些?

任务拒绝模块是线程池的保护部分,线程池有一个最大的容量,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池。

拒绝策略是一个接口,其设计如下:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

用户可以通过实现这个接口去定制拒绝策略,也可以选择JDK提供的四种已有拒绝策略,其特点如下:
拒绝策略

问题12:线程池的参数可以动态调整吗?
  • public void setCorePoolSize(int corePoolSize)
  • public void setMaximumPoolSize(int maximumPoolSize)
  • public void setKeepAliveTime(long time, TimeUnit unit)
问题13:线程池被创建后里面有线程吗?如果没有的话,你知道有什么方法对线程池进行预热吗?
  • public boolean prestartCoreThread() 预热一个核心线程
  • public int prestartAllCoreThreads() 预热所有的核心线程
问题14:核心线程数会被回收吗?需要什么设置?
public void allowCoreThreadTimeOut(boolean value)
问题15:一个线程池中的线程抛出了未经捕获的运行时异常,那么线程池会怎么处理这个线程?
  1. 当执行方式是execute时,可以看到堆栈异常的输出
  2. 当执行方式是submit时,堆栈异常没有输出(内部捕获了)。但是调用Future.get()方法时,可以捕获到异常
  3. 不会影响线程池里面其他线程的正常执行
  4. 线程池会把这个线程移除掉,并创建一个新的线程放到线程池中
  5. 线程池饱和之后跑出拒绝异常RejectException怎么办?一样的可以看到堆栈异常的输出

线程发生未捕获的异常后,最终都会走到Thread#dispatchUncaughtException方法
判断当前线程是否设置了UncaughtExceptionHandler,设置了会回调它的uncaughtException方法,否则回调线程组的uncaughtException方法

// ThreadGroup.java
public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
        	// 这里判断是否设置了DefaultUncaughtExceptionHandler
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
            	// 设置了,则回调uncaughtException()方法
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
            	// 没有设置,则打印日志输出
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
}
问题16:线程池的关闭,shutdown和shutdownNow区别?
  • shutdown:不再接受新任务,线程池中的任务会继续处理完
  • shutdownNow:不再接受新任务,会中断线程池中的正在执行任务的线程
问题17:线程池的扩展和监控有哪些?

线程池对开发者提供了几组方法,我们可以监控到任务的执行状态。这些方法在 ThreadPoolExecutor.java

  • beforeExecute
// 线程池中每个任务执行前,会调用此方法
protected void beforeExecute(Thread t, Runnable r) { }
  • afterExecute
// 线程池中每个任务执行前,会调用此方法
protected void afterExecute(Runnable r, Throwable t) { }
  • terminated
// 线程池终止了
protected void terminated() { }
  • 可暂停执行的线程池
// 引用ThreadPoolExecutor.java提供的例子
public class PausableThreadPoolExecutor extends ThreadPoolExecutor {
    private boolean isPaused;
    private ReentrantLock pauseLock = new ReentrantLock();
    private Condition unpaused = pauseLock.newCondition();

    public PausableThreadPoolExecutor(...) {
        super(...);
    }

    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        pauseLock.lock();
        try {
            while (isPaused) unpaused.await();
        } catch (InterruptedException ie) {
            t.interrupt();
        } finally {
            pauseLock.unlock();
        }
    }

    public void pause() {
        pauseLock.lock();
        try {
            isPaused = true;
        } finally {
            pauseLock.unlock();
        }
    }

    public void resume() {
        pauseLock.lock();
        try {
            isPaused = false;
            unpaused.signalAll();
        } finally {
            pauseLock.unlock();
        }
    }
}}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值