Java 线程池的创建方式与实践

本文深入探讨了Java线程池的创建与使用,包括ExecutorService的原始创建,Executors工厂类的四种线程池类型,以及Spring提供的ThreadPoolTaskExecutor。详细分析了各种线程池的优缺点,强调了避免使用Executors创建线程池的原因,并提出了线程执行方式的差异。最后,给出了线程池实践中的应用场景和注意事项。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:以下内容是自己学习与总结,如有不准确的地方,欢迎指点。

涉及内容:
ThreadPoolExecutor
ExecutorService
ThreadPoolTaskExecutor


一.ExecutorService创建线程池(最原始)

//JDK提供
//工厂类Executors中也使用到该类线程池
ExecutorService pool=new ThreadPoolExecutor(
        0,      //corePoolSize = 0
        Integer.MAX_VALUE,  //maximumPoolSize = 2147483647
        60L,   //keepAliveTime = 60
        TimeUnit.MILLISECONDS,
        new SynchronousQueue<Runnable>(),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());

二.工厂类Executors快速创建线程池(最简单)

Executors:线程池的工具类,相当于一个工厂类,用来创建合适的线程池,返回ExecutorService类型的线程池,由java.util提供

分别是:缓存线程池、固定大小线程池、单线程线程池、可延时或定时执行的线程池

##1.可缓存线程池:

//适用于大量短耗时任务和对响应时间要求较高的场景
//允许的创建线程数量为 Integer.MAX_VALUE(无限),可能会创建大量的线程
//添加任务时会优先使用空闲的线程,如果没有就创建一个新线程,线程数没有上限,所以每一个任务都会马上被分配到一个工作线程进行执行,不需要在阻塞队列中等待;
//如果线程池长期闲置,那么其中的所有线程都会被销毁,节约系统资源。
ExecutorService executorService=Executors.newCachedThreadPool();
优点缺点
1.任务在添加后可以马上执行,不需要进入阻塞队列等待;
2.在闲置时不会保留线程,可以节约系统资源
对线程数没有限制,可能会过量消耗系统资源;

2.定长线程池:

//任务量峰值不会过高,且任务对响应时间要求不高的场景
//定长线程池中的线程数会逐步增长到nThreads个,并且在之后空闲线程不会被释放,线程数会一直保持在nThreads个。
//如果添加任务时所有线程都处于忙碌状态,那么就会把任务添加到阻塞队列中等待执行,阻塞队列中任务的总数没有上限。
ExecutorService executorService=Executors.newFixedThreadPool(int nThreads)
优点缺点
线程数固定,对系统资源的消耗可控1.任务量暴增的情况下线程池不会弹性增长,会导致任务完成时间延迟;
2.使用了无界队列,在线程数设置过小的情况下可能会导致过多的任务积压,引起任务完成时间过晚和资源被过度消耗的问题

3.延时任务线程池:

//可以提供延时执行、定时执行等功能
//实现了ScheduledExecutorService接口,主要用于需要延时执行和定时执行的情况。
ScheduledExecutorService executorService=Executors.newScheduledThreadPool(int corePoolSize);
优点特点
可以提供延时执行、定时执行等功能1.最大线程数为无限,在任务量较大时可以创建大量新线程执行任务;
2.超时时间为0,线程空闲后会被立即销毁;
3.使用了延时工作队列,延时工作队列中的元素都有对应的过期时间,只有过期的元素才会被弹出;

4.单线程线程池:

//单线程线程池中只有一个工作线程,可以保证添加的任务都以指定顺序执行(先进先出、后进先出、优先级)
ExecutorService executor<br> =Executors.ScheduledThreadPool();
优点特点
可以保证添加的任务都以指定顺序执行相比与手动创建线程:1.可以通过共享的线程池很方便地提交任务进行异步执行,而不用自己管理线程的生命周期;
2.我们可以使用任务队列并指定任务的执行顺序,容易对任务进行管理

三.ThreadPoolTaskExecutor简洁创建线程池(常用)

由Spring提供,可通过配置和自动注入的方式来构建线程池其底层也是适应使用的Executor,但提供的方法更多、可配置的参数也相对丰富:

ThreadPoolTaskExecutor poolTaskExecutor=new ThreadPoolTaskExecutor();
        poolTaskExecutor.setQueueCapacity(0); //这个地方随便填
        poolTaskExecutor.setCorePoolSize(0);  //核心线程数是0,阻塞队列会选择new SynchronousQueue()
        poolTaskExecutor.setMaxPoolSize(2147483647);  // Integer.MAX_VALUE = 2147483647
        poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        poolTaskExecutor.setKeepAliveSeconds(60);
        poolTaskExecutor.initialize();

四.总结

建议:

  • 1.线程池尽量不要使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,因为Executors 返回的线程池对象的弊端如下
    ** 1) FixedThreadPool 和 SingleThreadPool:

    允许的请求队列长度为 Integer.MAX_VALUE(无限),可能会堆积大量的请求,从而导致 OOM。
    ** 2) CachedThreadPool:

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

  • 2 线程的执行方式的区别
    execute(…) 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

    submit(…) 方法用于提交需要返回值的任务。
    线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

五.实践

提示:线程池的实践,处理了单一线程线程池、多线程线程池、并发、异步
点击下面超链接查看:
使用单一线程线程池、多线程线程池、异步线程,实现作业的同步和异步执行,并实现高并发的处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值