2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > Java线性池的使用方式以及核心运行原理

Java线性池的使用方式以及核心运行原理

时间:2020-08-30 19:39:58

相关推荐

Java线性池的使用方式以及核心运行原理

Java线性池的使用方式以及核心运行原理

一、为什么需要线性池?二、线性池的处理流程三、线程池的使用(ThreadPoolExecutor)四、线程池的注意事项

一、为什么需要线性池?

java为了提高并发度,可以使用多线程共同执行,但是如果有大量的线程在短时间被创建或者销毁,就会占用大量的系统时间,影响系统效率。

有了线性池以后,线程创建后在指定的时间内统一归系统管理,而不是在执行时创建,执行完销毁。从而避免了系统频繁创建、销毁线程而带来的系统开销。

二、线性池的处理流程

1.先判断核心线程池里的核心线程们是否空闲,如果空闲就把这个新的任务给空闲的核心线程执行,如果没有空闲并且核心线程数小于corePoolSize,则创建新的核心线程去执行这个任务。

2.如果线程池的线程数已达到核心线程数,并且这些线程都繁忙,则把这个新的任务放到等待队列中去。如果等待队列已经满了,则判断线程数是否达到maximumPoolSize,如果没达到,则创建新的线程执行任务。

3.如果到达了,则交给RejectedExecutionHandler(拒绝策略)来决定如何处理这个新的任务。

三、线程池的使用(ThreadPoolExecutor)

在java里,线程池的概念就是Executor这个接口,具体实现是ThreadPoolExecutor类,这个类是线性池中最核心的一个类,如果要透彻了解线性池,就必须先了解这个类。

ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器:

Constructor 描述 ThreadPoolExecutor​(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) 创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂和拒绝的执行处理程序。 ThreadPoolExecutor​(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂。 ThreadPoolExecutor​(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) 使用给定的初始参数和默认拒绝的执行处理程序创建一个新的 ThreadPoolExecutor 。 ThreadPoolExecutor​(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 用给定的初始参数创建一个新的 ThreadPoolExecutor 。

ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

下面解释下一下构造器中各个参数的含义:

1.corePoolSize(线程池的基本大小)

当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

2.runnableTaskQueue(任务队列)

用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。

SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。

PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

3.maximumPoolSize(线程池最大大小)

线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

4.ThreadFactory:用于设置创建线程的工厂

可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。

5.RejectedExecutionHandler(饱和策略)

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。

AbortPolicy:直接抛出异常。CallerRunsPolicy:只用调用者所在线程来运行任务。DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。DiscardPolicy:不处理,丢弃掉。.当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

6.keepAliveTime(线程活动保持时间)

线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

7.TimeUnit(线程活动保持时间的单位)

可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

四、线程池的注意事项

虽然线程池能大大提高服务器的并发性能,但使用它也会存在一定风险。与所有多线程应用程序一样,用线程池构建的应用程序容易产生各种并发问题,如对共享资源的竞争和死锁。此外,如果线程池本身的实现不健壮,或者没有合理地使用线程池,还容易导致与线程池有关的死锁、系统资源不足和线程泄漏等问题。

1) 建议使用new ThreadPoolExecutor(…)的方式创建线程池

线程池的创建不应使用Executors 去创建,而应该通过 ThreadPoolExecutor创建,这样可以让读者更加明确地知道线程池的参数设置、运行规则,规避资源耗尽的风险,这一点在也阿里巴巴JAVA开发手册中也有明确要求。这一点不容小觑,曾有同学因为线程池使用不当导致生产的同一台机器上部署的多个应用都因无法创建线程池而出现故障。

2) 合理设置线程数

①CPU密集型:

定义:CPU密集型的意思就是该任务需要大量运算,而没有阻塞,CPU一直全速运行。

CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程)。

CPU密集型任务配置尽可能少的线程数。

CPU密集型线程数配置公式:(CPU核数+1)个线程的线程池

②IO密集型:

定义:IO密集型,即该任务需要大量的IO,即大量的阻塞。

在单线程上运行IO密集型任务会导致浪费大量的CPU运算能力浪费在等待。

所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间。

- 第一种配置方式:

由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。

配置公式:CPU核数 * 2。

- 第二种配置方式:

IO密集型时,大部分线程都阻塞,故需要多配置线程数。

配置公式:CPU核数 / (1 – 阻塞系数)(0.8~0.9之间)

比如:8核 / (1 – 0.9) = 80个线程数(10倍)

3) 设置能代表具体业务的线程名称

这样方便通过日志的线程名称识别所属业务。具体实现可以通过指定ThreadPoolExecutor的ThreadFactory参数,如使Spring提供的CustomizableThreadFactory

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。