java线程治理法宝一:线程池

本文详细介绍了线程池的工作原理,包括线程复用、控制并发数和管理线程的优势。探讨了Java中线程池核心类ThreadPoolExecutor的参数设置,如corePoolSize、maxPoolSize等,并分析了四种常见线程池类型的特点。此外,还讨论了线程池的拒绝策略和创建线程池的注意事项。

线程池使用及优势

  1. 线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,
    如果线程数量超过了最大数量 超出数量的 线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
  2. 它的主要特点为:线程复用,控制最大并发数,管理线程

优点:

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

线程池核心类(和参数)

在java.util.concurrent包中我们能找到线程池的定义,其中ThreadPoolExecutor是我们线程池核心类,首先看看线程池类的主要参数有哪些。

  • corePoolSize:线程池的核心大小,也可以理解为最小的线程池大小。
  • maxPoolSize:最大线程池大小。
  • keepAliveTime:空余线程存活时间,指的是超过corePoolSize的空余线程达到多长时间才进行销毁。
  • unit:销毁时间单位。
  • workQueue:存储等待执行线程的工作队列。
  • threadFactory:创建线程的工厂,一般用默认即可(阿里规约不建议使用默认,理由是:创建线程或线程池时请指定有意义的线程名称,方便出错时回溯)。
  • handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。

线程池工作流程

1、如果线程池中的线程小于corePoolSize时就会创建新线程直接执行任务。
2、如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。
3、如果工作队列workQueue也满时,当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略(见下文)。

(常用)线程池分类

Executors是jdk里面提供的创建线程池的工厂类,它默认提供了4种常用的线程池应用,而不必我们去重复构造。

newSingleThreadExecutor

单线程线程池,核心线程数和最大线程数均为1,空闲线程存活0毫秒同样无意思,意味着每次只执行一个线程,多余的先存储到工作队列,一个一个执行,保证了线程的顺序执行。

newFixedThreadPool
固定线程池,核心线程数和最大线程数固定相等,而空闲存活时间为0毫秒,说明此参数也无意义,工作队列为最大为Integer.MAX_VALUE大小的阻塞队列。当执行任务时,如果线程都很忙,就会丢到工作队列等有空闲线程时再执行,队列满就执行默认的拒绝策略。

newCachedThreadPool
带缓冲线程池,从构造看核心线程数为0,最大线程数为Integer最大值大小,超过0个的空闲线程在60秒后销毁,SynchronousQueue这是一个直接提交的队列,意味着每个新任务都会有线程来执行,如果线程池有可用线程则执行任务,没有的话就创建一个来执行,线程池中的线程数不确定,一般建议执行速度较快较小的线程,不然这个最大线程池边界过大容易造成内存溢出。

newScheduledThreadPool
调度线程池,即按一定的周期执行任务,即定时任务,对ThreadPoolExecutor进行了包装而已。

线程池使用的注意事项

Executors返回的线程池对象的弊端如下:

1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

拒绝策略

  • AbortPolicy
    简单粗暴,直接抛出拒绝异常,这也是默认的拒绝策略。
  • CallerRunsPolicy
    如果线程池未关闭,则会在调用者线程中直接执行新任务,这会导致主线程提交线程性能变慢。
  • DiscardPolicy
    从方法看没做任务操作,即表示不处理新任务,即丢弃。
  • DiscardOldestPolicy
    抛弃最老的任务,就是从队列取出最老的任务然后放入新的任务进行执行。

提交线程任务

可以先随便定义一个固定大小的线程池 

ExecutorService es = Executors.newFixedThreadPool(3);
//提交一个线程
es.submit(xxRunnble);
es.execute(xxRunnble);

提交方式submit和execute区别

  1. execute没有返回值,如果不需要知道线程的结果就使用execute方法,性能会好很多。
  2. submit返回一个Future对象,如果想知道线程结果就使用submit提交,而且它能在主线程中通过Future的get方法捕获线程中的异常。

关闭线程池相关方法

shutdown();  不再接受新的任务,之前提交的任务等执行结束再关闭线程池。

shutdownNow();  不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程list列表。

awaitTermination(3L, TimeUnit.SECONDS);监测3秒内线程是否结束isTerminated();表示判断shutdown();是否优雅结束完毕

创建线程池示例

根据阿里规约来,创建线程池需要自定义的线程工厂,那么先就创建线程工厂

package com.example.java.base.concurrency.threadpool.factory;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * UserThreadFactory:用户自定义线程工厂(微服务下回创建多个ThreadFactory,前缀已模块命名,比如用户微服务那么就叫UserThreadFactory)</br>
 * 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。</br>
 * 说明:Executors返回的线程池对象的弊端如下:</br>
 * 1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。</br>
 * 2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
 *
 * @author zhangxiaoxiang
 * @date 2021/04/21
 */
@Slf4j
public class UserThreadFactory implements ThreadFactory {
    private String namePrefix;
    private final AtomicInteger nextId = new AtomicInteger(1);

    /**
     * @param whatFeatureOfGroup 线程组名称(传入机房编号或者业务模块名)
     */
    public UserThreadFactory(String whatFeatureOfGroup) {
        namePrefix = "UserThreadFactoryPool-" + whatFeatureOfGroup + "-Worker-";
    }

    /**
     * 构造一个新的Thread 。 实现也可以初始化优先级,名称,守护程序状态, ThreadGroup等
     *
     * @param r 任务
     * @return 创建的线程
     */
    @Override
    public Thread newThread(Runnable r) {
        //线程全名:线程(工厂)池-线程组(机房编号或业务模块)-Worker-线程编号
        String name = namePrefix + nextId.getAndIncrement();
        //group –线程组.如果为null且存在安全管理器,则该组由SecurityManager.getThreadGroup()确定.如果没有安全管理器或SecurityManager.getThreadGroup()返回null,则该组将设置为当前线程的线程组。
        //target –启动此线程时调用run方法的对象。 如果为null ,则调用此线程的run方法。
        //name –新线程的名称
        //stackSize –新线程所需的堆栈大小,或为零以指示该参数将被忽略
        //InheritThreadLocals-如果为true ,则从构造线程继承可继承线程局部变量的初始值,否则不继承任何初始值
        Thread thread = new Thread(null, r, name, 0, false);
        log.info(thread.getName());
        return thread;
    }


}

创建几个示例任务,用于模拟任务执行

package com.example.java.base.concurrency.threadpool.task;

import lombok.extern.slf4j.Slf4j;

/**
 * DemoTask:示例任务
 *
 * @author zhangxiaoxiang
 * @date 2021/4/21
 */
@Slf4j
public class DemoTask implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("线程名称:【{}】执行了任务", Thread.currentThread().getName());
    }

}

最后创建线程池

package com.example.java.base.concurrency.threadpool;

import com.example.java.base.concurrency.threadpool.factory.UserThreadFactory;
import com.example.java.base.concurrency.threadpool.task.DemoTask;
import com.example.java.base.concurrency.threadpool.task.ExecutorTask;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * A10UserThreadPool:自定义线程池
 *
 * @author zhangxiaoxiang
 * @date 2021/04/21
 */
@Slf4j
public class A10UserThreadPool {
    public static void main(String[] args) {
        //获取CPU核数
        final int nThreads = Runtime.getRuntime().availableProcessors();
        //默认拒绝策略是AbortPolicy, 抛出RejectedExecutionException拒绝任务的处理程序
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                6,
                6,
                10L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new UserThreadFactory("outbound"),
                //显示创建默认拒绝策略(尽量保证线程池的完整创建,有利于理解)
                new ThreadPoolExecutor.AbortPolicy());
        //示例:默认核心线程6个,最大核心线程数也是6,队列10,那么同时处理的最大任务数是16,下面的for循环提交20个任务,就或超标,要么采用拒绝策略,要么加大线程或者队内容量
        //Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7403c468*****
        //多线程下同时处理任务的数量区间[0,队列长度+maximunPoolSize],根据任务的处理,分CPU密集型(计算密集型)和IO密集型(数据库文件网络等操作),所以这里核心线程是和最大核心线程数取值就不一样
        //CPU型建议:corePollSize=系统核心数+1
        //IO型建议:corePollSize=系统核心数*2
        // for (int i = 0; i < 20; i++) {
        //     poolExecutor.submit(new ExecutorTask());
        // }
        for (int i = 0; i < 7; i++) {
            //7个任务完全可以执行,且有空闲
            poolExecutor.submit(new ExecutorTask());
        }

        try {
            //测试keepAliveTime参数
            Thread.sleep(12000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //在上面完全执行的情况下,线程池还有空闲才可以提交任务
        for (int i = 0; i < 20; i++) {
            //超标的任务就丢弃
            poolExecutor.submit(new DemoTask());
        }
    }
}

执行结果

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值