Java创建线程池的几种方式具体实现

Java基础 潘老师 2年前 (2021-10-13) 1200 ℃ (0) 扫码查看

在讲解Java创建线程池的几种方式之前,潘老师先带大家了解一些关于线程池的概念和原理。

一、Java线程池是什么

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务,并且线程池中的线程都是后台线程。

二、Java线程池作用意义

第1:降低资源消耗。通过重复利用机制已降低线程创建和销毁造成的消耗。
第2:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第3:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

三、Java四种线程池的使用

JDK1.5中引入的Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行。Java通过Executors为我们提供了四种线程池的创建方式,分别为:
1)newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

// 可缓存线程池 Executors表示启动线程的  可创建线程数是无限大小的
 ExecutorService executorService = Executors.newCachedThreadPool();
 for (int i = 0; i < 10; i++) {
     final int temp = i;
     // 可执行线程  execute 启动线程
     executorService.execute(new Runnable() {
         public void run() {
             System.out.println(Thread.currentThread().getName() + "," + temp);
         }
     });
 }
 //停止线程池
 executorService.shutdown();

2)newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

//创建可固定长度的线程池,只会创建5个线程池进行处理
 ExecutorService executorService = Executors.newFixedThreadPool(5);
 for (int i = 0; i < 20; i++) {
     final int temp = i;
     // 可执行线程  execute 启动线程
     executorService.execute(new Runnable() {
         public void run() {
             System.out.println(Thread.currentThread().getName() + "," + temp);
         }
     });
 }
 //停止线程池
 executorService.shutdown();

3)newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。

//创建可定时执行的线程池 数量为3
 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
 for (int i = 0; i < 10; i++) {
     final int temp=i;
     //schedule 方法表示线程执行  表示延迟5秒之后 开始执行线程
     scheduledExecutorService.schedule(new Runnable() {
         public void run() {
             System.out.println(Thread.currentThread().getName()+""+temp);
         }
     }, 5, TimeUnit.SECONDS);
 }
 //停止线程池
 scheduledExecutorService.shutdown();

4)newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

//创建单线程线程池
 ExecutorService executorService = Executors.newSingleThreadExecutor();
 for (int i = 0; i < 10; i++) {
     final int temp = i;
     // 可执行线程  execute 启动线程
     executorService.execute(new Runnable() {
         public void run() {
             System.out.println(Thread.currentThread().getName() + "," + temp);
         }
     });
 }
 //停止线程池
 executorService.shutdown();

查看Executors类源码,以newSingleThreadExecutor 方法为例:
Java创建线程池的几种方式具体实现
我们发现其实都是在使用ThreadPoolExecutor类进行构建的,然后该类目构造方法有很多参数。这些参数很重要,需要掌握,面试也会经常被问到!

四、ThreadPoolExecutor线程池类7大参数详解

参数 含义
corePoolSize 核心线程数量,线程池维护线程的最少数量
maximumPoolSize 线程池维护线程的最大数量
keepAliveTime 线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁
unit keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS
workQueue 线程池所使用的任务缓冲队列
threadFactory 线程工厂,用于创建线程,一般用默认的即可
rejectHandler 线程池拒绝任务时的处理策略

五、Java线程池执行流程

一个线程提交到线程池后的具体处理流程如下图所示:
Java创建线程池的几种方式具体实现
1)如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

2)如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。

3)如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

4)如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

5)当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

总结:处理任务判断的优先级为 核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler采取任务拒绝策略进行处理;
线程池的拒绝策略:当最大线程数 + 队列缓存数量 小于线程数量的时候,程序运行出错,被拒绝。(在此不再详细展开)

六、Java自定义线程池

虽然JDK的Executors提供了4大类线程池供我们直接使用,但是在实际生产开发中,往往是不允许/不建议使用这4种直接调用的方式的,比如在阿里巴巴的Java开发手册中就有如下【强制】要求:
Java创建线程池的几种方式具体实现
原因就是因为直接创建会容易导致OOM(内存溢出~)
推荐我们手工自定义线程池,在自定义时我们需要预估线程数多少合适,主要参考依据为核心线程数计算公式:

  • IO密集型:核心线程数 = CPU核数 / (1-阻塞系数)
  • CPU密集型:核心线程数 = CPU核数 + 1
  • IO密集型:核心线程数 = CPU核数 * 2

可以创建个工具类如下:

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolUtil {
    /**
     * 默认 CPU 核心数
     */
    private static int threadPoolSize = 0;

    static {
        // 获取服务器 CPU 核心数
        threadPoolSize = Runtime.getRuntime().availableProcessors();
        System.out.println(" CPU 核心数量:" + threadPoolSize);
    }

    public static int getThreadPoolSize() {
        return threadPoolSize;
    }

    /**
     * 线程工厂,用来创建线程
     */
    private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-demo-%d").setDaemon(true).build();

    /**
     * 设置线程池核心参数(IO 密集型) 核心线程数 = CPU 核数 * 2
     * 参数依次分别为上面的的7大参数,最后拒绝策略参数可以不写,则默认使用AbortPolicy
     */
    private static ThreadPoolExecutor threadPoolExecutorIO =
        new ThreadPoolExecutor(threadPoolSize, threadPoolSize * 2, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());

    /**
     * 设置线程池核心参数(CPU 密集型) 核心线程数 = CPU 核数 + 1 //  核心线程数 = CPU 核数 + 1
     */
    private static ThreadPoolExecutor threadPoolExecutorCPU =
        new ThreadPoolExecutor(threadPoolSize, threadPoolSize + 1, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());

    /**
     * 返回线程池对象
     *
     * @return
     */
    public static ThreadPoolExecutor getThreadPoolExecutorIO() {
        return threadPoolExecutorIO;
    }
    public static ThreadPoolExecutor getThreadPoolExecutorCPU() {
        return threadPoolExecutorCPU;
    }
}

如果你不想区分CPU密集型还是IO密集型,只是想简单的自己手工创建一个线程池,比如我这里只想创建一个SingleThreadExecutor,则代码如下:

ExecutorService executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadFactoryBuilder().setNameFormat("thread-pool-demo-%d").setDaemon(true).build());
for (int i = 0; i < 10; i++) {
     final int temp = i;
     // 可执行线程  execute 启动线程
     executorService.execute(new Runnable() {
         public void run() {
             System.out.println(Thread.currentThread().getName() + "," + temp);
         }
     });
 }

以上就是Java创建线程池的几种方式具体实现全部内容,如果你有什么疑问,可以评论留言~


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/java/3855.html
喜欢 (2)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】