java并发编程线程池详解及使用场景(附带项目中使用线程池实现异步工具)

ThreadPoolExecutor类详解

使用线程池的目的是:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。所以需要统一管理

1.构造和参数

在ThreadPoolExecutor类中四个构造方法:
在这里插入图片描述
前三个都是调第四个构造方法,参数也最多如下
在这里插入图片描述

  • corePoolSize:核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量

  • maximumPoolSize:线程池维护线程的最大数量

  • keepAliveTime:线程池维护线程所允许的空闲时间,当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。

  • unit:线程池维护线程所允许的空闲时间的单位,参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
    在这里插入图片描述

  • workQueue:一个阻塞队列,用来存储等待执行的任务,常用队列如下

    ArrayBlockingQueue;
    LinkedBlockingQueue;
    SynchronousQueue;
    
  • threadFactory:线程工厂,主要用来创建线程

  • handler:线程池中的数量大于maximumPoolSize,对拒绝任务的处理策略,取值如下

    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 
    

2.调用关系
在这里插入图片描述
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法
在这里插入图片描述
ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等
在这里插入图片描述

Executor是顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的

在这里插入图片描述

3.ThreadPoolExecutor类中重要方法

  • execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
  • submit()用来向线程池提交任务,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用Future来获取任务执行结果
  • shutdown():不会立即关闭线程池,但也不接受新的任务,等待队列中所有任务执行完毕后关闭。
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,清空工作队列,返回尚未执行的任务。
  • 其他的方法:getQueue(),getPoolSize(),getActiveCount(),getCompletedTaskCount()等获取与线程池相关属性的方法

线程池实现原理

在这里插入图片描述

线程池状态
在这里插入图片描述
Worker
在这里插入图片描述
实现runnable,继承aqs,处理线程调度用的,具体源码自己去看比较多

execute()源码

//通过execute向线程池提交任务public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();    //如果当前线程数未达到核心线程数,则直接创建线程来执行新任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }    //否则将任务加入阻塞队列,这里进行双重检查,如果线程池已经关闭,则调用reject(),    //如果当前线程池线程数为0,则新建线程
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }    //如果加入阻塞队列失败,则尝试新建一个线程,如果失败了    //则说明线程池关闭了或者线程达到最大线程数,因此调用reject()
    else if (!addWorker(command, false))
        reject(command);
}

简单实例

public class Test {
     public static void main(String[] args) {   
         ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                 new ArrayBlockingQueue<Runnable>(5));
          
         for(int i=0;i<15;i++){
             MyTask myTask = new MyTask(i);
             executor.execute(myTask);
             System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
             executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
         }
         executor.shutdown();
     }
}
 
 
class MyTask implements Runnable {
    private int taskNum;
     
    public MyTask(int num) {
        this.taskNum = num;
    }
     
    @Override
    public void run() {
        System.out.println("正在执行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"执行完毕");
    }
}

结果
在这里插入图片描述

当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程

使用线程池实现异步工具

@Component
@Slf4j
public class AsyncUtils {

    private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(1, 5, 60,
            TimeUnit.MINUTES, new ArrayBlockingQueue<>(512));
    @Resource
    private AsyncTaskMapper asyncTaskMapper;

    public void execute(Runnable runnable, String param, String taskDesc) {
        AsyncTaskDO asyncTaskDO = AsyncTaskDO.builder()
                .param(param)
                .taskDesc(taskDesc)
                .build();
        asyncTaskMapper.insert(asyncTaskDO);
        EXECUTOR.submit(new TenantRunnableWrap(runnable, asyncTaskMapper, asyncTaskDO));

    }

    @Slf4j
    static class TenantRunnableWrap implements Runnable {
        private final Runnable task;
        private final AsyncTaskMapper asyncTaskMapper;
        private final AsyncTaskDO asyncTaskDO;

        public TenantRunnableWrap(Runnable task, AsyncTaskMapper asyncTaskMapper, AsyncTaskDO asyncTaskDO) {
            this.task = task;
            this.asyncTaskMapper = asyncTaskMapper;
            this.asyncTaskDO = asyncTaskDO;
        }

        @Override
        public void run() {
            try {
                task.run();
            } catch (Exception e) {
                asyncTaskDO.setExceptionDesc(e.getMessage());
                asyncTaskMapper.updateById(asyncTaskDO);
                return;
            }
            asyncTaskMapper.deleteById(asyncTaskDO);
        }
    }
}

使用方法如下,传参的目的是统计出异常的时候存表,对于异步任务不好解决报错提供了一个思路
在这里插入图片描述

如何合理配置线程池的大小

在这里插入图片描述

阿里巴巴Java开发手册中明确指出,而且用的词是『不允许』使用Executors创建线程池:

不允许使用Executors创建线程池
在这里插入图片描述【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors各个方法的弊端:

1)newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
  
2)newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

Positive example 1//org.apache.commons.lang3.concurrent.BasicThreadFactory
    ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
       
            
Positive example 2ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
 
    //Common Thread Pool
    ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
 
    pool.execute(()-> System.out.println(Thread.currentThread().getName()));
    pool.shutdown();//gracefully shutdown
           
            
Positive example 3<bean id="userThreadPool"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="2000" />
 
    <property name="threadFactory" value= threadFactory />
        <property name="rejectedExecutionHandler">
            <ref local="rejectedExecutionHandler" />
        </property>
    </bean>
    //in code
    userThreadPool.execute(thread);

参考:
Java并发编程:线程池的使用

阿里巴巴开发规范——不允许使用Executors来创建线程池

阿里巴巴Java开发手册(详尽版).pdf

Java异步并发线程池是一种用于管理和执行多线程异步任务的机制。通过使用线程池,可以有效地控制系统资源,并提高并发性能。核心线程数是线程池一直存在的线程数量,它们准备就绪并等待异步任务的执行。可以使用ExecutorService接口的实现类Executors来创建线程池,例如使用newFixedThreadPool方法创建一个固定大小的线程池,如下所示:ExecutorService service = Executors.newFixedThreadPool(10); \[1\] 关于Java异步并发和线程池的更多信息,可以参考以下资源: - 参考1:https://wenku.baidu.com/view/a9cdf1c09889680203d8ce2f0066f5335a81672a.html - 参考2:https://www.cnblogs.com/weilx/p/16329743.html \[3\] #### 引用[.reference_title] - *1* *2* [Java异步线程池](https://blog.csdn.net/weixin_47409774/article/details/123610455)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Java异步并发和线程池](https://blog.csdn.net/qq_36330274/article/details/127229455)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值