首页 > 编程知识 正文

为什么使用线程池(多线程线程池)

时间:2023-05-03 22:32:43 阅读:102401 作者:4802

为什么要使用线程池

在计算机中,线程是昂贵的资源。频繁的线程创建和线程销毁会消耗大量的程序性能,尤其是在一些高并发的场景中。引入线程池后,线程的创建和销毁由线程池控制,空闲的线程可以在线程池中重用,避免了频繁创建和销毁线程带来的不必要的性能消耗。

四种线程池

newScheduledThreadPool:可调度线程池,支持定时和周期任务的执行;newFixedThreadPool:定长线程池,可以控制最大并发线程数;超过限制的线程将在队列中等待newCachedThreadPool:可缓存线程池;如果线程池长度超过处理需求,可以灵活回收闲置线程。如果没有回收,创建一个新的线程newSingleThreadExecutor:单线程池,只使用唯一的工作线程执行任务,并确保所有任务按照指定的顺序(FIFO、LIFO、优先级)执行

线程池原理

。一、执行器接口的UML图:

执行器_UML

注意执行人和被执行人的区别。

1.Executor是线程池的顶部接口,只有一个空的方法execute(),没有返回值。

2.Executors是一个提供工厂方法来创建不同类型线程池的类。

二、核心参数:

CorePoolSize:字面意思是核心线程数maxmunPoolSize:线程池中可以创建的最大线程数keepAliveTime:线程保持空闲的时间长度,一般用在可缓存线程池中TimeUnit: time unit ThreadFactory:线程工厂,可以指定线程名称BlockingQueue: work queue。一般有四种,即同步队列、链接锁定队列、数组锁定队列和优先级锁定队列。稍后,我们将讨论这三种队列的使用以及它们之间的区别:RejectedExecutionHandler:一般来说,有四种策略。它们是澳大利亚政策、所谓的反政策、放弃政策、放弃理想政策。首先,我们需要对这些参数有一点印象。通过下面的解释,我们将逐渐了解这些参数的用法。

三.工作流程

初始化线程池时,线程池中没有线程。如果要在初始化时创建线程,可以调用prestartAllCoreThreads()来创建所有的核心线程,调用prestartCoreThread()来创建一个核心线程来实现目标。当线程池接收到请求的任务时,它将启动核心线程来处理该任务。在请求高峰期,如果没有空闲线程,线程池将继续创建新线程来处理请求,直到达到核心线程的数量。如果达到了核心线程的数量,并且仍然没有空闲线程,并且请求继续进入,则新的请求被放入工作队列。加入时,请求不断进入,没有空闲线程,工作队列已满,那么线程池将继续创建线程。如果仍然没有空闲线程,并且请求不断进入,则拒绝策略将被触发。在这个过程中,如果一个线程空闲,它不会创建新的线程,而是让这个线程处理任务,从而达到线程复用的目的。如果请求高峰期已经过去,许多线程将处于空闲状态。这时,空闲的时间就会派上用场。在空闲时间,线程仍将驻留在线程池中。如果超过空闲时间,线程池将回收空闲线程。四.参数分析1:阻塞队列

BlockingQueue:这是一个双缓冲队列。BlockingQueue在内部使用两个队列,允许两个线程同时存储和提取队列。在保证并发安全性的同时,提高了队列的访问效率。

指定大小的ArrayBlockingQueue(int I): BlockingQueue必须用指定的大小构造。它包含的对象是按先进先出顺序大小不固定的LinkedBlockingQueue()或(int i): BlockingQueue。如果在其构造中指定了大小,则生成的BlockingQueue有大小限制,大小由Integer决定。最大值。它所包含的对象是按照FIFO顺序排序的,这也是阿里巴巴代码规范在创建线程池时要指定参数的原因,以免因为没有指定队列大小而导致OOM。PriorityBlockingQueue()或(int i):类似于LinkedBlockingQueue,但它包含的对象顺序不是FIFO,而是由对象的自然顺序或构造函数的比较器决定的。SynchronizedQueue():特殊阻塞队列,其操作必须通过放和取交替进行。

成。

参数解析二:RejectedExecutionHandler

上面提到了四种拒绝策略,分别是:

AbortPolicy:线程池的默认拒绝策略,即丢弃任务并抛出RejectedExecutionException异常CallerRunsPolicy:由调用线程处理该任务,如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。

创建线程池


根据阿里巴巴代码规约:

Alibaba代码规约

意思是说,通过以下代码创建线程池会有安全隐患

ScheduledExecutorService scheduExec = Executors.newScheduledThreadPool(3);

此处只指定了核心线程数大小,没有指定其他参数,我们来看看newScheduledThreadPool源码

1、这是newScheduledThreadPool()源码

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }

2、构造了一个ScheduledThreadPoolExecutor对象,继续深入

public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }

3、跟踪到父类构造器

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }

头条对代码的优化不够好,大家有兴趣可以自己跟下源码

可以看到,我们只传了核心线程数大小,没有指定最大线程数大小,默认最大线程数大小为Integer.MAX_VALUE,在高并发场景下势必会造成OOM。而且在newFixedThreadPool定长线程池和newSingleThreadExecutor中会由于队列长度为Integer.MAX_VALUE而造成OOM。所以安全起见,我们创建线程池采用以下方式:

创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 创建线程池的时候请使用带ThreadFactory的构造函数, 并且提供自定义ThreadFactory实现或者使用第三方实现。 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); ExecutorService singleThreadPool = new ThreadPoolExecutor(1,1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); singleThreadPool.execute( ()-> System.out.println(Thread.currentThread().getName())); singleThreadPool.shutdown(); public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); … }

关闭线程池有两个方法,分别是:

shutdown:只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断shutdownNow:则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。