关于线程池创建记录

一、线程池的创建分为两大类:

1、通过 Executors 创建

2、通过 ThreadPoolExecutor 创建

以上这两类创建线程池的方式有 7 种具体实现方法,这 7 种方法便是本文要说的创建线程池的七种方式。分别是:

(1)、Executors.newFixedThreadPool()   创建一个大小固定的线程池,可控制并发的线程数,超出的线程会在队列中等待

(2)、Executors.newCachedThreadPool()    创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程
(3)、Executors.newSingleThreadExecutor()    创建单个线程的线程池,可以保证先进先出的执行顺序
(4)、Executors.newScheduledThreadPool()    创建一个可以执行延迟任务的线程池
(5)、Executors.newSingleThreadScheduledExecutor()    创建一个单线程的可以执行延迟任务的线程池
(6)、Executors.newWorkStealingPool()    创建一个抢占式执行的线程池
(7)、ThreadPoolExecutor()    手动创建线程池,可自定义相关参数

二、execute和submit的区别

  • execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务
  • submit既能提交Runnable类型任务也能提交Callable类型任务。
  • execute()没有返回值
  • submit有返回值,所以需要返回值的时候必须使用submit

三、Runnable和Callable区别
方法签名不同,void Runnable.run(), V Callable.call() throws Exception
是否允许有返回值,Callable允许有返回值
是否允许抛出异常,Callable允许抛出异常。
Callable是JDK1.5时加入的接口,作为Runnable的一种补充,允许有返回值,允许抛出异常。

四、关于ThreadPoolExecutor详解:

1、为什么使用线程池?
为了减少创建和销毁线程的次数,让每个线程都可以多次的使用,可以根据系统情况调整线程的数量,防止消耗过多内存。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,使用线程池就可以优化。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

2、线程池的核心参数

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                          int maximumPoolSize,//最大线程数
                          long keepAliveTime,//线程空闲时间
                          TimeUnit unit,//时间单位
                          BlockingQueue<Runnable> workQueue,//任务队列
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler//拒绝策略) 

4.线程池的执行顺序
线程池按以下行为执行任务

  • 当线程数小于核心线程数时,创建线程。
  • 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  • 当线程数大于等于核心线程数,且任务队列已满,若线程数小于最大线程数,创建线程。
  • 若线程数等于最大线程数,则执行拒绝策略

5.线程池的参数详解

corePoolSize
核心线程数,默认为1。
设置规则:
CPU密集型(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1
IO密集型(与cpu密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2
maximumPoolSize
最大线程数,默认为Integer.MAX_VALUE
一般设置为和核心线程数一样
keepAliveTime
线程空闲时间,默认为60s,一般设置为默认60s
unit
时间单位,默认为秒
workQueue
队列,当线程数目超过核心线程数时用于保存任务的队列。(BlockingQueue workQueue)此队列仅保存实现Runnable接口的任务。(因为线程池的底层BlockingQueue的泛型为Runnable)
无界队列
队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,而博主踩到的就是这个坑,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,导致cpu和内存飙升服务器挂掉。
当然这种队列,maximumPoolSize 的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
有界队列
当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
(*注* 使用PriorityBlockingQueue 队列需要注意Comparator比较器:可能会报java.util.concurrent.FutureTask cannot be cast to java.lang.Comparable,这时你的任务中就需要去实现一下Comparable<ThreadTask> 这个接口:public class ThreadTask implements Runnable, Comparable<ThreadTask> )
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
同步移交队列
如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
原文:https://blog.csdn.net/riemann_/article/details/104704197
threadFactory
线程工厂,用来创建线程。
为了统一在创建线程时设置一些参数,如是否守护线程,线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。
它是一个接口类,而且方法只有一个,就是创建一个线程。
如果没有另外说明,则在同一个ThreadGroup 中一律使用Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY 优先级和非守护进程状态。
通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。
如果从newThread 返回 null 时ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务
handler
拒绝策略,默认是AbortPolicy,会抛出异常。
当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
AbortPolicy 丢弃任务,抛运行时异常。
CallerRunsPolicy 由当前调用的任务线程执行任务。
DiscardPolicy 忽视,什么都不会发生。
DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。

线程池ThreadPoolExecutor详解(整理详细)_折腾原理,日渐秃顶的博客-CSDN博客

关于ThreadPoolTaskExecutor是spring封装的线程池

线程池ThreadPoolExecutor详解(整理详细)_折腾原理,日渐秃顶的博客-CSDN博客

newFixedThreadPool:

        使用的构造方式为new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()),设置了corePoolSize=maxPoolSize,keepAliveTime=0(此时该参数没作用),无界队列,任务可以无限放入,当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常
newSingleThreadExector:

        使用的构造方式为new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0),基本同newFixedThreadPool,但是将线程数设置为了1,单线程,弊端和newFixedThreadPool一致
newCachedThreadPool:

        使用的构造方式为new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue()),corePoolSize=0,maxPoolSize为很大的数,同步移交队列,也就是说不维护常驻线程(核心线程),每次来请求直接创建新线程来处理任务,也不使用队列缓冲,会自动回收多余线程,由于将maxPoolSize设置成Integer.MAX_VALUE,当请求很多时就可能创建过多的线程,导致资源耗尽OOM
newScheduledThreadPool:

        使用的构造方式为new ThreadPoolExecutor(var1, 2147483647, 0L, TimeUnit.NANOSECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue()),支持定时周期性执行,注意一下使用的是延迟队列,弊端同newCachedThreadPool一致

所以根据上面分析我们可以看到,FixedThreadPool和SigleThreadExecutor中之所以用LinkedBlockingQueue无界队列,是因为设置了corePoolSize=maxPoolSize,线程数无法动态扩展,于是就设置了无界阻塞队列来应对不可知的任务量;而CachedThreadPool则使用的是SynchronousQueue同步移交队列,为什么使用这个队列呢?因为CachedThreadPool设置了corePoolSize=0,maxPoolSize=Integer.MAX_VALUE,来一个任务就创建一个线程来执行任务,用不到队列来存储任务;SchduledThreadPool用的是延迟队列DelayedWorkQueue。

!注*  线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors返回的线程池对象的弊端如下:
1) FixedThreadPool和singleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致0OM。
2) CachedThreadPool和 scheduledThreadPool:
允许的创建线

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;

public class MyThreadPool {
    //日志级别(由高到低):fatal -> error -> warn -> info -> debug,低级别的会输出高级别的信息,高级别的不会输出低级别的信息
    private static final Logger log = LoggerFactory.getLogger(MyThreadPool.class);

    //构建线程池
    public static ThreadPoolExecutor pool = null;

    //向线程池中添提交任务,无参数返回
    //判断核心线程数数量,阻塞队列,创建非核心线程数,拒绝策略
    public static void execute(Runnable runnable){
        getThreadPool().execute(runnable);
    }
    //向线程池中添提交任务,将任务返回
    //判断核心线程数数量,阻塞队列,创建非核心线程数,拒绝策略
    public static <T> Future<?> submit(Runnable runnable){
        //提交任务并将任务返回
        Future<?> future = getThreadPool().submit(runnable);
        //将任务存储在hash表中
        return future;
    }

    /**
     * io密集型:最大核心线程数为2N,可以给cpu更好的轮换,
     *           核心线程数不超过2N即可,可以适当留点空间
     * cpu密集型:最大核心线程数为N或者N+1,N可以充分利用cpu资源,N加1是为了防止缺页造成cpu空闲,
     *           核心线程数不超过N+1即可
     * 使用线程池的时机:1,单个任务处理时间比较短 2,需要处理的任务数量很大
     * 参考:https://blog.csdn.net/yuyan_jia/article/details/120298564
     */
    public static synchronized ThreadPoolExecutor getThreadPool(){
        if(pool == null){
            //获取当前机器的cpu
            int cpuNum = Runtime.getRuntime().availableProcessors();
            System.out.println("当前机器的cpu的个数为:" + cpuNum);
            int maximumPoolSize = cpuNum * 2;
            pool = new ThreadPoolExecutor(
                    maximumPoolSize - 2,
                    maximumPoolSize,
                    5L,//5s
                    TimeUnit.SECONDS,
                    new PriorityBlockingQueue<>(), //数组有界队列
                    Executors.defaultThreadFactory(), //默认的线程工厂
                    new ThreadPoolExecutor.AbortPolicy()
            );
        }
        return pool;
    }
}

程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致oOM.anrenxiang

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值