加入收藏 | 设为首页 | 会员中心 | 我要投稿 拼字网 - 核心网 (https://www.hexinwang.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

线程的高 效利用:线程池介绍

发布时间:2022-12-14 12:48:45 所属栏目:Linux 来源:
导读:  线程的开销:

  常见的线程池(比如数据库连接池)的实现方式是对象池内部维护一定数量的对象,客户端代码需要一个对象的时候就向对象池申请一个对象,用完之后再将该对象返还给对象池,于是对象池中的一个
  线程的开销:
 
  常见的线程池(比如数据库连接池)的实现方式是对象池内部维护一定数量的对象,客户端代码需要一个对象的时候就向对象池申请一个对象,用完之后再将该对象返还给对象池,于是对象池中的一个对象就可以先后为多个客户端线程服务。线程池本身也是一个对象,不过它的实现方式与普通的线程池不同;线程池内部可以预先创建一定数量的工作者线程,客户端代码并不需要向线程池借用线程而是将其需要执行的任务作为一个对象提交给线程池,线程池可能将这些任务缓存在队列之中,而线程池内部的各个工作者线程则不断地从队列中去除任务并执行。因此,线程池可以被看做基于生产者-消费者模式的一种服务,该服务内部维护的工作者线程相当于消费者线程,线程池的客户端线程相当于生产者线程,客户端代码提交给线程池的任务相当于“数据”,线程池内部用于缓存任务的队列相当于传输通道;
 
  线程 线程池_线程池linux_linux c线程池
 
  java.util.concurrent.ThreadPoolExecutor类就是一个线程池,客户端代码可以调用ThreadPoolExecutor.submit方法向其提交任务,ThreadPoolExecutor.submit方法声明如下:
 
  public Future submit(Runnble task);
  task参数是一个Runnable实例,它代表客户端需要线程池代为执行的任务;
 
  线程池内部维护的工作线程的数量就被称为该线程池的线程池大小(Pool Size)。ThreadPoolExecutor的线程池大小有三种形态:
 
  public ThreadPoolExecutor(int corePoolSize,
     int maximumPoolSize,
     long keepAliveTime,
     TimeUnit unit,
     BlockingQueue<Runnable> workQueue,
     ThreadFactory threadFactory,
     RejectedExecutionHandler handler);
  RejectedExecutionHandler:
 
  在初始状态下,客户端每提交一个任务线程池就创建一个工作者线程来处理该任务。随着客户端不断地提交任务,当前线程池大小也相应增加。在当前线程池大小达到核心线程池大小的时候,新来的任务就会被存入工作队列之中。这些缓存的任务由线程池中的所欲偶工作者线程负责取出进行执行。线程池将任务存入工作队列的时候调用的是BlockingQueue的非阻塞方法offer(E e),因此工作队列满并不会使提交任务的客户端线程暂停。当工作队列满的时候,线程池会继续创建新的工作者线程,直到当前线程池大小达到最大线程池大小。线程池通过调用threadFactory.newThread方法来创建工作者线程的。如果我们在创建线程池的时候没有指定线程工厂,那么ThreadPoolExecutor会使用Executors.defaultThreadFactory()所返回的默认线程工厂,当线程池饱和时,既工作者队列满了并且线程池大小达到最大线程池大小的情况下,客户端试图提交的任务会被拒绝(reject)。为了提高线程池的可靠性,Java标准库引入了一个RejectExecutionHandler接口用于封装被拒绝的任务的处理策略,该接口定义了如下方法:
 
  void rejectedExecution(Runnable r,ThreadPoolExecutor executor);
  其中,r代表被拒绝的任务,executor代表拒绝任务r的线程池实例。可以通过线程池的构造器或者setRejectExecutionHandler(RejectedExecutorHandler handler)方法来为线程池关联一个RejectedExecutorHandler。当客户端提交的任务被拒绝时,线程池所关联的RejectedExecutionHandler的rejectExecution方法会被线程池调用;
 
  实现类所实现的处理策略
 
  ThreadPoolExecutor.AbortPolicy
 
  直接抛出异常
 
  ThreadPoolExecutor.DiscardPolicy
 
  丢弃当前被拒绝的任务(而不抛出任何异常)
 
  ThreadPoolExecutor.DiscardOldestPolicy
 
  将工作队列中最老的任务丢弃,然后重新尝试接纳被拒绝的任务
 
  ThreadPoolExecutor.CallerRunsPolicy
 
  在客户端线程中执行被拒绝的任务
 
  在当前线程池大小超过线程池核心大小的时候,超过线程池核心大小部门的工作者线程空闲时间达到keepAliveTime所指定的时间后就会被清理掉,既这些工作者线程会自动终止并被从线程池中移除。这种空闲线程清理机制有利于节约有限的线程资源,但keepAliveTime值设置不合理(特别是设置得太小)可能导致工作者线程频繁地清理和创建反而增加了开销;
 
  线程池中数量上等于核心线程池大小的那部分工作者线程,习惯上我们称之为核心线程;当前线程池大小是随着线程池接收到的任务的数量而逐渐向核心线程池大小靠拢的,既核心线程是逐渐被创建与启动的。ThreadPoolExecutor.prestartAllCoreThreads()则使得我们可以使线程池在未接收到任何任务的情况下预先创建并启动所以的核心线程,这样可以减少任务被线程池处理时所需的等待时间(等待核心线程的创建与启动);
 
  ThreadPoolExecutor.shutdown()/shutdownNow()方法可以用来关闭线程池。使用shutdown()关闭线程池的时候,已提交的任务会被继续执行,而新提交的任务会像线程池饱和那样被拒绝掉。ThreadPoolExecutor.shutdown()返回的时候可能线程池还未关闭,既线程池中可能还有工作者线程在执行任务。应用代码可以通过调用ThreadPoolExecutor.awaitTermination(long timeout,TimeUnit unit)来等待线程池关闭结束,使用ThreadPoolExecutor.shutdownNow()关闭线程池的时候,正在执行的任务会被停止,已提交而等待执行的任务也不会被执行;该方法的返回值是已提交而未被执行的任务列表,这为被取消的任务的重试提供了一个机会。由于ThreadPoolExecutor.shutdownNow()内部是通过调用工作线程的interrupt方法来停止正在执行的任务的,因此某些无法响应中断的任务可能永远也不会停止。反过来说,在关闭线程池的时候如果我们能够确保已经提交的任务都已执行完毕并且没有新的任务会被提交,那么调用ThreadPoolExecutor.shutdownNow()总是安全可靠的;
 
  线程池监控:
 
  尽管线程池的大小,工作队列的容量,线程空闲时间限制这些线程池的属性可通过配置的方式进行制定(而不是硬编码在代码中),但是所指定的值是否恰当则需要通过监控来判断。例如,如果我们选择有界队列作为工作队列,那么这个队列的容量设置多少为合适,这需要在软件测试过程中对线程池进行监控来确定,另外考虑到测试环境和软件实际运行环境总是存在差别的,由于软件运维的考虑我们也可能需要对线程池进行监控;
 
  方法用途
 
  getPoolSize()
 
  获取当前线程池的大小
 
  getQueue()
 
  返回工作队列实例
 
  getLargestPoolSize()
 
  获取工作者线程曾经到达的最大数
 
  getActiveCount()
 
  获取线程池中当前正在执行任务的工作者线程(相近值)
 
  getTaskCount()
 
  获取线程池到目前为止所接收到的任务数(相近值)
 
  getCompletedTaskCount()
 
  获取线程池到目前为止已经处理完毕的任务数(相近值)
 
  此外,TheadPoolExecutor提供的两个钩子方法:beforeExecute(Thread t,Runnable r)和afterExecute(Thread t,Runnable r)也能用于实现监控;设e为任意一个ThreadPoolExecutor实例,在任意一个任务r被线程池e中的任意一个工作者线程t执行前,e.beforeExecute(t,r)会被执行;不管r的执行结果是否成功还是抛出异常,e.afterExecute(t,r)始终会被执行。因此,如果有必要的话我们可以通过创建ThreadPoolExecutor的子类并在子类的beforeExecute/afterExecute方法实现监控逻辑,比如计算任务执行的平均耗时;
 
  线程池死锁:
 
  如果线程池中执行的任务在其执行期间又向同一个线程池提交另外一个任务,而前一个任务的执行结束又依赖于后一个任务的执行结果,那么就有可能出现这样的场景:线程池中的所有工作者线程都处于等待其他任务的处理器结果而这些任务仍在工作队列中等待执行,这时由于线程池中已经没有可以对工作队列中的任务进行处理的工作线程,这种等待就会一直持续下去而形成死锁
 
  因此,适合提交给同一线程池执行的任务是相互独立的任务,而不是彼此有依赖关系的任务。对于彼此存在依赖关系的任务,可以考虑分别使用不同的线程池实例来执行这些任务;
 
  工作者线程的异常终止:
 
  如果任务是通过ThreadPoolExecutor.submit调用提交给线程池的,那么这些任务在其执行过程中即便是抛出了未捕获的异常也不会导致对其进行执行的工作者线程异常终止。如果任务是通过ThreadPoolExecutor.execute方法提交给线程池的,那么这些任务在其执行过程中一旦抛出了未捕获的异常,则对其进行执行的工作者线程就会异常终止。尽管ThreadPoolExecutor能够侦测到这种情况并在工作者线程异常终止的时候创建并启动新的替代工作者线程线程池linux,但是由于线程的创建与启动都是有开销的,因此这种情况下应该经量避免任务在其执行过程中抛出未捕获的异常。我们可以通过ThreadPoolExecutor的构造器参数或者ThreadPoolExecute.setThreadFactory方法为线程池关联一个线程工厂,在这个线程工厂中可以为其创建的线程关联一个UncaughtExceptionHandler,通过这个关联的UncaughtExceptionHandler我们可以侦测到任务执行过中抛出的未捕获异常。不过,由于ThreadPoolExecutor内部实现的原因,只有通过ThreadPoolExecutor.execute调用(而不是ThreadPoolExecutor.submit调用)提交给线程池执行的任务,其执行过程中抛出的未捕获异常才会导致UncaughtExceptionHandler.uncaughtException方法被调用。
 

(编辑:拼字网 - 核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!