Java 线程池

本文详细介绍了Java线程池的概念、优势,包括资源节省、响应速度提升和线程管理优化。重点讲解了Executor和ThreadPoolExecutor接口,以及Executors工厂类提供的四种常见线程池类型:cached、single、fixed和scheduled。此外,还涉及了线程池的工作流程和面试问题。
摘要由CSDN通过智能技术生成

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

线程池有如下的优势:

(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

Executor:

线程池的顶级接口。

ExecutorService:

对Executor的功能扩展。

ThreadPoolExecutor:

Java里面线程池的顶级接口是 Executor,不过真正的线程池接口是 ExecutorService,ExecutorService 的默认实现是 ThreadPoolExecutor;

Executors:

普通类,Executors,线程池创建工厂类(生产什么的工厂),Executors 类提供工厂方法用来创建不同类型的线程池。比如: newSingleThreadExecutor() 创建一个只有一个线程的线程池,newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。


Executors 提供四种线程池:

1)newCachedThreadPool  

线程池根据需求创建线程,可扩容,遇强则强(银行一共7个窗口,只开放了3个窗口,其他4个窗口没有开放,如果人很多就要开放其余的窗口,高峰结束再恢复到3个窗口状态)

    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.

    特点:

    1、线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)

    2、线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)

    3、当线程池中,没有可用线程,会重新创建一个线程

    场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景

2)newSingleThreadExecutor 

一个任务一个任务执行,一池一线程(例如窗口只能服务一个人,后面来的只能等待)

    创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,

    此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

    特点: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此执行

    场景: 适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景

3)newFixedThreadPool 

一池N线程

    创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变。

    特征:

    1、线程池中的线程处于一定的量,可以很好的控制线程的并发量

    2、线程可以重复被使用,在显示关闭之前,都将一直存在

    3、超出一定量的线程被提交时候需在队列中等待

    场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景

 4)newScheduledThreadPool (了解)

    创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

    场景: 适用于需要多个后台线程执行周期任务的场景


实际项目开发中也是推荐使用手动创建线程池的方式,而不用默认方式,关于这点在《阿里巴巴开发规范》中是这样描述的:

OOM:out of memory 内存溢出


线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), handler);
}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

其实就是一个方法的多种重写,总共有以下几个参数:

  1. corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  2. maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  3. keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  4. unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  5. workQueue(必需):阻塞任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  6. threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  7. handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。


线程池的工作流程:

// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                             MAXIMUM_POOL_SIZE,
                                             KEEP_ALIVE,
                                             TimeUnit.SECONDS,
                                             sPoolWorkQueue,
                                             sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        ... // 线程执行的任务
    }
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表


面试题

1)什么是 Executor 框架?

Executor框架在Java 5中被引入,Executor 框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架。

无限制的创建线程会引起应用程序内存溢出,所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用 Executor 框架可以非常方便的创建一个线程池。

2)Executors 类是什么?

Executors为Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类提供了一些工具方法。Executors 可以用于方便的创建线程池。

好的博客:

Java 多线程:彻底搞懂线程池_孙强 Jimmy的博客-CSDN博客_java 多线程池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值