Java线程池的创建与使用 2021-09-11

Java 异步并发与池化技术


一、线程池是什么?

项目开发中,不会使用直接创建线程的方式,因为直接创建线程实现方式无法控制线程,可能会造成系统资源耗尽,浪费系统资源,造成系统的性能下降; 在企业业务开发中,必须使用线程池的方式,构建多线程;让线程充分利用,降低系统 的资源的消耗。

1.1 线程从创建到消耗经历那些过程

我们需要执行一个 java 任务,可以直接 new Thread 来运行任务,线程从创建到消耗经历那些过程:
1、创建 java 线程实例,线程是一个对象实例,堆内存中分配内存(创建线程需要消耗 时间和内存) 2、执行 start 方式启动线程,操作系统为 Java 线程创建对应的内核线程,线程处于就 绪状态(内核线程是操作系统的资源,创建需要时间和内存)
3、线程被操作系统 cpu 调度器选中后,线程开始执行(run 方法开始运行)
4、JVM 开始为线程创建线程私有资源:JVM 虚拟机栈*程序计数器(需要时间和内存)
5、线程运行过程中,cpu 上下文切换 (消耗时间,频繁切换,影响性能)
6、线程运行完毕,Java 线程被垃圾回收器回收 (销毁线程内存需要时间)

1.2 直接创建与线程池对比

  • 从线程执行的流程来看:
    1、线程不仅是 java 对象,更是操作系的资源(创建线程,消耗线程都需要时间)
    2、Java 线程的创建和运行都需要内存空间(线程数量太多,消耗很多内存)
    3、cpu 上下文切换(线程数量一大,cpu 频繁切换)
  • 线程池优势:
  • 1、降低系统的资源的消耗
  • 2、提供系统的响应速度
  • 3、方便管理(线程复用,控制最大并发数,管理线程) 事先准备好一些资源,用人要用(业务系统要是有线程),就来我这里拿(线程池中获取),用完之后不能销毁,必须还给我(线程池线程可复用性);

二、创建线程池

2.1 线程池的使用

代码示例:

	    // 1、创建线程池对象;创建单个线程的线程池对象
	    ExecutorService executorService1 = Executors.newSingleThreadExecutor();
	    // 2、创建固定数量的线程池(指定核心线程数数量),核心线程数为 2
	    ExecutorService executorService2 = Executors.newFixedThreadPool(2);
	    //3、创建一个按照计划执行的线程池
	    ScheduledExecutorService executorService3 = Executors.newScheduledThreadPool(2);
	    //4、创建一个自动增长的线程池
	    ExecutorService executorService4 = Executors.newCachedThreadPool();
        // 线程执行
        try {
            for (int i = 0; i < 10; i++) {
                executorService1.execute(() -> {
                    log.info("Executors创建线程池的方式实现多线程.......");
                    // 业务代码执行
                    int j = 100 / 3;
                    log.info("业务代码执行结果:{}", j);
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally { 
            //线程池用完,关闭线程池
            executorService1.shutdown();
        }
    }

2.2 参数说明

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
  • 参数解析:
    1、corePoolSize: 线程池核心线程数,初始化线程池时候,会创建核心线程等待状态,核心线程不会被销毁,提供线程的复用;
    2、maximumPoolSize: 最大线程数;核心线程用完了,必须新建线程执行任务,但是新建的线程不能超过最大线程数;
    3、keepAliveTime:线程存活时间,除了核心线程以为 (maximumPoolSize- corePoolSize)的线程存活时间;当线程处于空闲状态, 他可以活多久;
    4、unit: 存活时间单位
    5、workQueue:任务阻塞队列,任务可能会很多,线程就那么几个, 因此可以把多余的任务放入队列进行缓冲,队列采用 FIFO 的,等待线程空闲, 再从队列取出任务执行;

不建议使用以下的创建线程池的方式: 原因是以下的线程池创建的方式,当线程量一大后,可能造成无限制创建线程,从而导致内存被占满,线程量大导致性能严重下降,甚至 OOM;

2.3 使用ThreadPoolExecutor 类创建线程池

2.3.1 参数说明

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • threadFactory: 线程工厂,默认使用 defaultthreadFactory, 用来创建线程的,一般使用默认即可,其中定义了线程的名字;
  • RejectedExecutionHandler: 线程池拒绝策略,线程池核心线程数,最大线程数,等待队列:

四种拒绝策略:
1、 AbortPolicy : 新任务直接被拒绝,抛出异常: RejectedExecutionException;
2、DisCardPolicy: 队列满了,新任务忽略不执行,直接丢弃,不会抛出异 常
3、DisCardOldestPolicy: 队列满了,新任务尝试和等待最久的线程竞争, 也不会抛出异常;抛弃任务队列中等待最久任务,新任务直接添加到队列中
4、CallerRunPolicy: 新任务来临后,直接使用调用者所在线程执行任务即可。

2.3.2 参数设置

合理配置线程相关的参数: 最大线程数设置公式: 最大线程数 = (任务执行时间 / 任务 cpu 时间)*N

根据业务类型进行设置(cpu 密集型,Io 密集型)

  • 如果 CPU 密集型任务(所有任务都在内存中执行:没有磁盘的读写): 建议线程池最大数量设置为 N(cpu 核心数量)+1,最大的利用CPU能力,多出一个线程用于等待执行。
  • 如果 IO 密集型任务(大量磁盘读写任务):如果有 IO 操作,cpu 此时处于空闲状态, 最大线程数应该设置:2N+1

最终创建线程的方法

        // 可伸缩的线程池
        //Runtime.getRuntime().availableProcessors() 获取当前的CPU 数量
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
                9, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

三、队列使用

在 Java 并发编程中,使用线程池,还必须使用任务队列,让任务在队列中进行缓冲;可以 线程执行防洪泄流的效果,提升线程池处理任务能力;
通常在线程池中,使用的都是阻塞队列: FIFO,阻塞等待:等待队列中元素被消费,有空闲位置后再放入队列。。

  • 如何为线程池选择一个合适的队列?
    在这里插入图片描述
    基于 Java 一些队列实现特性:
    1、ArrayBlockingQueue : 基于数组实现的有界的阻塞队列 (有界的队列).
    2、LinkedBlockingQueue: 基于链表实现的有界阻塞队列
    3、PriorityBlockingQueue: 支持按照优先级排序的无界的阻塞的队列
    4、DelayQueue: 优先级实现的无界阻塞队列
    5、SynchronousQueue : 只存储一个元素,如果这个元素没有被消费,不能在向里面存 储元素
    6、LinkedTransferQueue: 基于链表实现的无界的阻塞队列
    7、LinkedBlockingDeque: 基于链表实现的双向的无界阻塞的队列

  • 如何选择一个合适的队列:
    建议必须使用有界队列,有界队列能增加系统的稳定性,根据需求设置大一些(可控设置);如果设置为无界队列,遇到不可控的因素,可能会导致队列中的任务越来越多,出现OOM的情况。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值