线程池ThreadPoolExecutor使用原理

 

本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片、视频等原文的内容)

若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cnblogs.com/wengshuhang/p/10077378.html

 

使用线程池的目的:

  1、多线程并发的场景,频繁地创建跟销毁线程,浪费了对应资源时间,线程池可以节约线程创建与销毁的时间,使线程直接进入业务实现。

  2、线程并发数量过多,抢占系统资源从而导致阻塞

       3、对线程进行一些简单的管理

线程池的风险:

  1、死锁:虽然任何多线程程序中都有死锁的风险,但线程池却引入了另一种死锁可能,在那种情况下,所有池线程都在执行已阻塞的等待队列中另一任务的执行结果的任务,但这一任务却因为没有未被占用的线程而不能运行。当线程池被用来实现涉及许多交互对象的模拟,被模拟的对象可以相互发送查询,这些查询接下来作为排队的任务执行,查询对象又同步等待着响应时,会发生这种情况。

  2、资源不足:线程池的一个优点在于:相对于其它替代调度机制(有些我们已经讨论过)而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。线程消耗包括内存和其它系统资源在内的大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。

如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。

  3、线程泄露:各种类型的线程池中一个严重的风险是线程泄漏,当从池中除去一个线程以执行一项任务,而在任务完成后该线程却没有返回池时,会发生这种情况。发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或一个 Error 时。如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。

有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。如果某个线程被这样一个任务永久地消耗着,那么它实际上就被从池除去了。对于这样的任务,应该要么只给予它们自己的线程,要么只让它们等待有限的时间。

ThreadPoolExecutor有4个构造方法

前边三个方法都调用了第四个方法,

详细参数:

1、corePoolSize:核心线程池大小

2、maximumPoolSize:池中允许的最大线程数

3、keepAliveTime :当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间

4、unit:keepAliveTime 参数的时间单位

5、workQueue : 执行前用于保持任务的队列。 这里平常用的有ArrayBlockingQueue(需要指定队列长度,底层实现四数组)跟LinkedBlockingQueue(可以不指定也可以指定长度,底层实现是链表,默认长度是Integer.max)。ps: 当核心线程数+队列等待数已经满了的情况下,再进入一条线程的请求时候,才会启动额外的线程资源,当最大线程数跟队列等待数都满了情况下,再进入一条线程请求将被丢弃。

6、threadFactory:线程工厂(不知道干嘛的,不管直接选默认的就好了=。=)

7、RejectedExecutionHandler:线程的丢弃策略(4种:

  1、AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)

  2、DiscardPolicy:也是丢弃任务,但是不抛出异常。 

  3、DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

  4、CallerRunsPolicy:由调用线程处理该任务 

说完构造方法,说说线程池的线程使用:

  实现runnable的线程直接使用ThreadPoolExecutor.execute(thread)就好咯

  若是想用future的线程,则有ExecutorService中三个方法

    <T> Future<T> submit(Callable<T> task); 

    <T> Future<T> submit(Runnable task, T result); 

    Future<?> submit(Runnable task);

第一个方法:submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
第二个方法:submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
第三个方法:submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。
下边贴上本地实验的代码

以上只是线程池的基本使用,接下来来看看线程池的部分源码。

  线程池一共有5种状态:

    

    

1、RUNNING

(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。 
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

2、 SHUTDOWN

(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3、STOP

(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING

(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、 TERMINATED

(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

ps:线程池使用AtomicInteger类型存储自己的运行状态以及容量,32位种前3位存储的是状态,后29存储着线程数量。

线程池的其他源码以及流程可以看看介个  https://www.cnblogs.com/qingquanzi/p/8146638.html

常见的四种线程池:

  1 Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务

  2  Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程。

  3  Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行

  4  Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

底层实现其实也是调用了ThreadPoolExecutor

 

posted @ 2018-12-06 16:21  星期天去哪玩o  阅读(327)  评论(0编辑  收藏  举报