java中的线程池

java中的线程池

java并发编程中运用场景最多的框架,主要优点是:
	1. 降低资源消耗:通过重复利用已创建的线程来减少线程创建和消耗的开销
	2. 提高响应速度:当任务到达时,可以不用等待线程创建就能立即执行
	3. 提高线程的可管理性:避免重复创建,可以对线程池内资源进行统一分配,调优和监控。

线程池的实现原理

  1. ThreadPoolExecutor的execute()方法的工作步骤:
    当有一个新的任务被提交到线程池时,
    • 线程池先判断核心线程池内的线程是否都在执行任务,如果不是,创建一个新的工作线程来执行任务,如果是,则进入下个流程(执行该步骤需要获取全局锁)
    • 判断工作队列BlockingQueue是否已经满了,如果不是,就将该任务加入队列,如果是,进入下个流程
    • 判断线程池的线程是否都处于工作状态,如果没有,创建一个新的工作线程来执行任务,如果是,就交给饱和策略来处理该任务。
      核心线程池的大小表示:线程池中允许同时运行的最大线程数。
  2. 工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环从工作队列中获取任务进行执行。

线程池的使用

  1. 线程池的创建
    new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler)
    • corePoolSize:核心线程池的大小,即线程池允许同时运行的最大线程数
    • runnableTaskQueue:阻塞队列,用于保存等待执行任务。
    • maxPoolSize:线程池允许创建的线程的最大数量。
    • keepAliveTime:线程活动保持时间。线程池的工作线程空闲后,保持的存活时间。当任务比较多时,并且每个人任务的执行时间比较短的时候,调大该参数,减少线程销毁再创建的开销来提高线程的利用率。
    • RejectedExecutionHandler饱和策略:当队列和线程池都满了的时候,必须采取一种策略来处理新的任务:
      • AbortPolicy:直接抛出异常,默认的策略
      • CallerRunPolicy:只用调用者所在的线程来运行任务
      • DiscardOldestPolicy:丢弃队列里最近的一个队列,并执行当前任务
      • DiscardPolicy:不处理,直接丢弃。
  2. 向线程池提交任务
    • execute()方法:用于提交不需要返回值的任务,所以无法判断任务是否被执行成功
    • submit()方法:用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get方法会阻塞当前线程直到任务完成,或者使用带超时设置的get方法。
  3. 关闭线程池
    1. 可以通过调用线程池的shutdown()或者shutdownNow()方法来关闭线程池。原理是:遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远也无法终止。
    2. shutdownNow首先将线程池的状态设置成Stop,然后尝试停止所有的正在执行或者暂停任务的线程,并返回等待执行任务的列表;而shutdown只是将线程池状态设置为shutdown,然后中断所有没有正在执行任务的线程。(shutdown会等待任务执行完毕,而shutdownNow会直接关闭)
    3. 只要调用了shutdown或者shutdownNow,isShutDown方法就返回true;只有等待所有的任务都已关闭,才表示线程池关闭成功,isTerminaed方法才会返回true。
  4. 如何配置线程池
    1. 根据不同的任务性质来分配不同规模的线程池。比如:
      CPU密集型任务应该分配尽可能小的线程池,因为cpu核心数=最大同时执行线程数,所以设置太多线程反而会带来线程切换的开销,因此设置为cpu的核心数或者cpu核心数+1就可以了。
      IO密集型任务:可以设置比较多的线程数,因为在IO的操作时,cpu是空闲的,因此通过增加线程池的线程数,可以不让cpu空闲浪费
    2. 根据任务的优先级来配置线程池:可以使用PriorityBlockingQueue来处理,它可以让优先级更高的任务先处理。
    3. 根据任务的执行时间来分配不同规模的线程池,或者使用优先级阻塞队列,让执行时间短的线程优先。
    4. 根据任务的依赖性来合理设置线程池的规模。比如,依赖数据库连接的任务,线程需要等待数据库的返回结果,此时CPU是空闲的,所以可以设置较多的线程来避免CPU的空闲浪费。
    5. 建议使用有界队列,避免因为外界原因,比如依赖数据库任务的阻塞,导致大量任务添加进入队列。有界队列有助于增加系统的稳定性和预警能力。
  5. 线程池的监控
    在大量使用线程池的系统中,有必要对线程池的状态进行监控,便于快速定位问题。
    1. taskCount:线程池需要执行的任务数
    2. completedTaskCount:线程池在运行中已经执行完的任务数,小于等于taskCount。
    3. largestPoolSize:线程池里曾经创建过的最大线程数量。
    4. getPoolSize:获取线程池的线程数量
    5. getActiveCount:获取活动的线程数
      通过扩展线程池进行监控,也可以通过继承线程池来实现自定义的线程池,重写beforeExecute()、afterExecute()、terminated()方法来进行监控。

补充:工具类的线程池的创建:

  1. 单个线程的线程池:适用于需要保证线程的顺序执行,并且在任意时间不会有多个线程是活动的应用场景。
    ExecutorService c = Executors.newSingleThreadPool();
  2. 固定数量的线程池,为了满足资源管理的需求,适用于负载比较重的服务器:
    ExecutorService c = Executors.newFixedThreadPool(20);
  3. 缓冲线程池:根据需要创建线程的无界的线程池。适用于执行很多的短期异步的小程序或者负载比较轻的服务器。
    ExecutorService c = Executors.newCachedThreadPool();
    • 底层使用的SynchronousQueue作为等待队列,因为SynchronousQueue是一个无容量的阻塞队列,也就意味着,一旦主线程提交任务的速度大于线程执行任务的速度,就会不断地去创建线程。极端情况下,会因为创建过多线程而耗尽CPU和内存资源。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值