线程池_初步认识_01

简介:

线程池_初步认识_01
  • 一、定义
    • 管理一组工作线程。
  • 二、好处
    • 1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗,比如内存;
    • 2. 提高响应速度。任务到达时,可以不需要等到线程创建就能执行;
    • 3. 提高线程的可管理性。通过线程池,实现对线程的统一分配,调优和监控;比如可以避免无线创建线程引起OutOfMemoryError。
  • 三、实现原理
    • 当向线程池提交一个任务之后,线程池是如何处理这个任务的呢?
      29543e9b-8af3-4573-a071-5f1a5705eda4-5210562.jpg
      • 上图就是线程池的主要处理流程;
      • ThreadPoolExecutor 执行execute()方法的四种情况(流程):
        • 1、如果当前运行的线程少于核心线程,则创建新线程来执行任务;(需要注意的是,此操作需要获取全局锁)
        • 2、如果运行的线程等于或对于核心线程,则将任务加入工作队列中;
        • 3、如果工作队列已满,则创建新的线程(非核心线程)来处理任务;(需要获取全局锁)
        • 4、如果创建线程超出最大线程数,则任务被拒绝,调用饱和策略;(RejectedExecutionHandler.rejectedExecution())
        • 总结: 如此设计的原因?
          • 为了在执行execute()方法时,尽可能地避免获取全局锁;因为获取全局锁是一个严重可伸缩瓶颈。
        • 期望: 每次任务提交的时候,都是当前运行的线程数大于等于核心线程数,此时不用获取全局锁,即加入工作队列。
  • 四、通过ThreadPoolExecutor创建线程池
    • public ThreadPoolExecutor(
      • int corePoolSize,
      • int maximumPoolSize,
      • long keepAliveTime,
      • TimeUnit unit,
      • BlockingQueue<Runnable> workQueue,
      • ThreadFactory threadFactory,
      • RejectedExecutionHandler defaultHandler)
    • 1. corePoolSize
      • 线程池的基本大小;如果要执行的任务数小于线程池基本大小,提交一个任务到线程池后,就会创建一个线程即便有空闲线程;否则,不创建,等待。
    • 2. maximumPoolSize
      • 线程池最大线程数。如果线程池中的线程数大于核心线程数并且队列满了,且线程数小于最大线程数,则会创建新的线程。
      • 如果maximumPoolSize与corePoolSize相等,即是固定大小线程池。
      • 如果使用无界队列,该参数无效;
    • 3. keepAliveTime
      • 空闲线程存活时间。
      • 默认情况下,当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用;当线程池中的线程空闲时,如果空闲时间等于keepAliveTime,线程会被销毁,直到线程数等于核心线程数,避免浪费内存和句柄资源。
      • 当ThreadPoolExecutor的allowCoreThreadTimeOut变量设置为true时,核心线程超时后也会被回收。
      • 如果任务多,并且每个任务的执行时间较短,增大时间,提高线程的利用率。
    • 4. unit
      • 时间单位
    • 5. BlockingQueue<Runnable> workQueue
      • 任务队列,用于保存等待执行的任务的阻塞队列。包括以下几种:
        • 1.ArrayBlockingQueue:一个基于数组结构的有界阻塞队列,FIFO(先进先出)原则对元素进行排序;
        • 2.LinkedBlockingQueue:一个基于链表结构的阻塞队列,也是先进先出,一般情况下吞吐量高于ArrayBlockingQueue 。 FixedThreadPool()和SingleThreadExecutor()使用此队列。
        • 3.SynchronousQueue:一个不存储元素的阻塞队列。上一个线程执行移除操作后,之后的插入操作才能执行,否则会一直处于阻塞状态。吞吐量高于LinkedBlockingQueue。CachedThreadPool()使用此队列。
        • 4.PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
    • 6. ThreadFactory threadFactory
      • 线程池创建线程使用的工厂;
      • 使用线程池创建线程的时候可以给予更有意义的名称,便于定位问题。
    • 7. RejectedExecutionHandler defaultHandler
      • 线程池对拒绝任务的处理策略;发生在队列和线程池都满了的时候。默认是AbortPolicy,表示无法处理新任务时抛出异常。
        • 1.AbortPolicy : 直接抛出异常;
        • 2.CallerRunsPolicy: 只用调用者所在线程来运行任务;
        • 3.DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务;
        • 4.DiscardPolicy:不处理,不丢弃;
    • 自定义拒绝策略实现RejectedExecutionHandler接口,实现需要的场景。比如持久化拒绝的任务、记录日志等等。
  • 五、提交任务到线程池的方法
    • (一)、 通过execute
      • executorService.execute(newRunnable() {
        • @Override
        • public void run() {
          • System.out.println("我要开始运行了");
        • }
      • });
      • 提交不需要返回值的任务,无法判断任务是否被线程池执行成功。
    • (二)、 通过submit
      • 1.通过提交Runnable
        • Future future =  executorService.submit(()->{});
      • 2.通过提交Callable
        • Future future =  executorService.submit(() -> {
          • return null;
        • });
      • 3.  Runnable 和 Callable 的区别
        • 1. 非常相似,这两个接口都表示可以由线程或ExecutorService同时执行的任务。
        • 2. 不同之处是两个接口内部执行的方法不同。
          • Runnable
            • public interface Runnable {
              • public void run();
            • }
          • Callable
            • public interface Callable{
              • public Object call() throws Exception;
            • }
        • - 可以看出,call()方法是有返回值的,而且可以引发异常。run没有返回值,也不能引发异常(除非未经检查的异常-RuntimeException的子类)。
      • 4.如果执行的任务需要返回结果,使用Callable。
    • (三)、两种执行方法的区别
      • 1. execute 只能提交Runnable类型的任务;submit 除了可以提交Runnable类型的任务外,还可以提交Callable类型。
      • 2. execute 直接抛出任务执行的异常;submit 会捕获,可以返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,通过get方法获取返回值,get()方法会阻塞当前线程直到任务完成,使用get(long timeout,TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候任务可能没有执行完。
      • 3. submit 的顶级接口是ExecutorService;execute 的顶级接口是 Executor;
  • 六、获取返回结果
    • (一)、 两种 invokeAny() 和 invokeAll()
    • (二)、invokeAny
      • 1. 当任意一个任务得到结果后,会调用interrupt方法将其他的任务中断;
      • 2. 部分任务失败,会使用第一个成功的任务返回的结果;
      • 3. 任务全部失败了,抛出Execption,invokeAny 方法将抛出ExecutionException。
    • (三)、invokeAll 返回所有任务的执行结果,该方法的执行效果也是阻塞执行的,要把所有的结果都取回时再继续向下执行。
  • 七、关闭线程池
    • 1、shutdown()
      • 1. 将线程池的状态设置成ShutDown;
      • 2. 中断所有没有正在执行任务的线程。在终止前允许执行以前提交的任务;
    • 2、shutdownNow()
      • 1. 将线程池的状态设置成Stop;
      • 2. 阻止等待任务的启动并试图停止当前正在执行的任务,并返回等待执行任务的列表;不允许执行以前提交的任务。
    • 1、2 两个方法的原理:
      • 遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
      • 调用1、2方法后,isShutdown 方法返回true。当所有任务都已关闭,才表示线程池关闭成功,调用isTerminaed方法返回true。
    • 3、awaitTermination()
      • 1. 接收timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经
      • 关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。
      • 在实际使用过程中, 使用shutdown()关闭,回收资源。如果有必要,可以在其后执行shutdownNow(),取消所有遗留的任务。
  • 八、配置线程池
    • 角度:
      • 1. 任务的性质: CPU密集型任务、IO密集型任务和混合型任务。
      • 2. 任务的优先级: 高、中与低;
      • 3. 任务的执行时间: 长、短;
      • 4. 任务的依赖性: 是否依赖其他系统资源,如数据库连接。
    • 选择:
      • 性质不同的任务可以用不同规模的线程池分开处理;
      • CPU 密集型任务应配置尽可能小的线程,如cpu数+1 的线程池;
      • IO密集型任务线程并不是一直在执行,则应配置极可能多的线程;
      • 优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理,它可以让优先级高的任务先执行;
        • 如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
      • 依赖数据库连接的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置的越大,这样才能更好低利用CPU。
    • 建议:
      • 有界队列;有界队列可以增加系统的稳定性和预警能力。
      • 体现:
        • 任务线程池的队列和线程池满了,不断抛出抛弃任务的异常,通过排查是数据库出现问题,导致执行sql的速度变慢,由于后台任务线程池里的任务都是需要向数据库插入数据的和查询,所以导致线程池里的工作线程全部阻塞,任务积压在线程池里。如果是无界队列,线程池中的队列越来越多,可能撑爆内存,导致系统不可用。
  • 八、例子
    • ExecutorService executorService = Executors.newSingleThreadExecutor();
      • Set<Callable<String>> callables = newHashSet<Callable<String>>();
        • callables.add(() -> {
          • return "Task1";
        • }});
        • callables.add(() -> {
          • return "Task2";
        • }});
      • List<Future<String>> futures =executorService.invokeAll(callables);
        • for(Future<String> future : futures){
          • System.out.println("future.get = " + future.get());
        • }
      • executorService.shutdown();
      • while (!service.awaitTermination(1, TimeUnit.SECONDS)) {
        • System.out.println("线程池没有关闭");
      • }

相关文章
|
6月前
|
缓存 Java
|
2月前
|
监控 Java API
如何快速地实现一个线程池
如何快速地实现一个线程池
21 0
|
Java
线程池总结
线程池总结
62 0
|
6月前
|
缓存 Java API
厉害了,线程池就该这么玩
厉害了,线程池就该这么玩
54 0
|
缓存 算法 Java
线程池和使用
线程池是一种用于管理和复用线程的机制。在多线程应用程序中,线程的创建和销毁需要消耗大量的系统资源,而线程池可以通过预先创建一定数量的线程,然后将任务分配给这些线程来避免频繁地创建和销毁线程,从而提高应用程序的性能和效率。线程池还可以控制并发线程的数量,避免过多的线程竞争资源导致的性能下降和系统崩溃。线程池是多线程编程中常用的一种技术,被广泛应用于各种类型的应用程序中。
76 0
线程池和使用
|
缓存 Java 调度
线程池的介绍
线程池的介绍
|
Java 调度
线程池 的一些事
线程池 的一些事
127 0
线程池 的一些事
|
缓存 NoSQL Java
【线程池】
【线程池】
148 0
|
存储 监控 Java
线程池
线程池
130 0