JAVA线程池专题(概念和作用)

线程池的作用

我们在用一个东西的时候,首先得搞明白一个问题。这玩意是干嘛的,为啥要用这个,用别的不行吗。那么一个一个解决这些问题

我们之前都用过数据库连接池,线程池的作用和连接池有点类似,频繁的创建,销毁线程会造成大量的不必要的性能开销,所以这个时候就出现了一个东西统一的管理线程,去负责线程啥时候销毁,啥时候创建,以及维持线程的状态,当程序需要使用线程的时候,直接从线程池拿,当程序用完了之后,直接把线程放回线程池,不需要去管线程的生命周期,专心的执行业务代码就行。

当然,如果非要是自己想手动new一个线程来执行,也不是不可以,只是像上面说的那样,第一麻烦,第二开销大,第三不好控制。

控制线程的方法

在说到线程池之前,首先要提到一个创建线程池的工具类,又或者说是工厂类 Executors 通过这个线程可以统一的创建线程,返回的是一个ExecutorService 类这个类中包含了一些对线程执行过程进行管理控制的方法;

void execute(Runnable command); 这个方法是将任务提交到线程池进行执行。这个方法没有返回值。

<T> Future<T> submit(Callable<T> task); 这个方法最特别的地方是线程执行完毕之后是有返回值的,另外方法的参数可以用Callable也可以为Runnable。可以适用于一些后续的代码,需要线程执行结果的程序。

下面的示例中,我们创建了一个 ExecutorService 的实例,提交了一个任务,然后使用返回的 Future 的 get() 方法等待提交的任务完成并返回值。

  ExecutorService executorService = Executors.newFixedThreadPool(10);
  Future<String> future = executorService.submit(() -> "Hello World");
  // 一些其它操作
  String result = future.get();

在实际使用时,我们并不会立即调用 future.get() 方法,可能会等待一些时间,推迟调用它直到我们需要它的值用于计算等目的。

ExecutorService 中的 submit() 方法被重载为支持 RunnableCallable ,它们都是功能接口,可以接收一个 lambdas 作为参数( 从 Java 8 开始 ):

  • 使用 Runnable 作为参数的方法不会抛出异常也不会返回任何值 ( 返回 void )
  • 使用 Callable 作为参数的方法则可以抛出异常也可以返回值。

如果想让编译器将参数推断为 Callable 类型,只需要 lambda 返回一个值即可。

  • void shutdown(); 在调用了shutdown方法之后,线程池就不会再接收新的任务,此时线程池还没有停止,仍然会把线程池中国正在执行但是还没有执行完的任务继续执行完毕,那些没有开始执行的任务则被中断
  • List<Runnable> shutdownNow(); 在调用了shutdownNow方法之后,会将线程池的状态设置为stop,正在执行的任务则被停止,没被执行任务的则返回。

这两种方法的使用场景:如果线程中的任务相互之间没有什么关联某个线程的异常对结果影响不大。那么所有线程都能在执行任务结束之后可以正常结束,程序能在所有task都做完之后正常退出,适合用ShutDown。但是,如果一个线程在做某个任务的时候失败,则整个结果就是失败的,其他worker再继续做剩下的任务也是徒劳,这就需要让他们全部停止当前的工作。这里使用ShutDownNow就可以让该pool中的所有线程都停止当前的工作,从而迫使所有线程执行退出。从而让主程序正常退出。

线程池的分类

通过工厂类 Executors 通过这个线程可以根据自己的需要统一的创建各种类型的线程,线程的分类大致分为以下四种:

  1. newSingleThreadExecutor
  2. CachedThreadPool
  3. newFixedThreadPool
  4. newScheduledThreadPool
  • newSingleThreadExecutor 创建一个单线程的线程池,核心线程和最大线程都为1,因此只会有一个工作线程,会按照指定顺序去执行,而且空闲时间为0,说明一旦没有任务了,线程就会被销毁
  • public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
                      0L, TimeUnit.MILLISECONDS,
                      new LinkedBlockingQueue<Runnable>()));
      }
    
    public class SinglePoolDemo {
      public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
    //    ExecutorService pool = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
          int finalI = i;
          pool.execute(() -> {
            System.out.println(Thread.currentThread().getName()+"----"+ finalI);
          });
        }
      }
    }
    

输出结果:

pool-1-thread-1----0
pool-1-thread-1----1
pool-1-thread-1----2
pool-1-thread-1----3
pool-1-thread-1----4
pool-1-thread-1----5
pool-1-thread-1----6
pool-1-thread-1----7
pool-1-thread-1----8
pool-1-thread-1----9

观察线程编号,可以发现,自始自终都只有一个线程在执行,并且也是按照顺序来执行的,。

  • CachedThreadPool 创建一个按需创建的线程,核心线程数为0,有一个最大线程数量,意味着可以根据实际任务数的需要,灵活的创建和管理线程,keepAlive时间为60s,代表当某线程超过60s空闲的时候,才会被销毁,这个线程池最特殊的地方在于,同步队列最多只能有一个元素,加入队列的线程会被马上执行。
  •  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                       60L, TimeUnit.SECONDS,
                       new SynchronousQueue<Runnable>());
      }
    
    public class CachePoolDemo {
      public static void main(String[] args) {
    
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 20000; i++) {
          int finalI = i;
          pool.submit(() -> {
            System.out.println(Thread.currentThread().getName()+"-------------"+finalI);
          });
        }
      }
    }
    

运行结果部分:

......
pool-1-thread-1805-------------19760
pool-1-thread-1806-------------19783
pool-1-thread-1809-------------19875
pool-1-thread-1810-------------19951
pool-1-thread-1811-------------19980

以上的代码我们运行了2w次线程任务,如果是按照我们之前的做法的话,我们要new 2w的线程去执行。通过这个不定长的线程池,他可以根据任务数来灵活的分配所创建的线程,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,所以这里只创建了大概1800多个线程就完成了我们原本需要new 2w个线程才能完成的任务,之所以说他是灵活分配的是因为,可以这样验证看看,把i的值改为20的话,所创建的线程数量大概是10以内,因此是根据任务数量来自行创建线程数的,可以保证效率和性能的最大化。

但是经过实测,这个灵活性虽然最高,但是性能貌似是相对比较差的,在两万任务数的条件下,所以他的缺点就是,可能会创建大量的线程。当然线程池这东西是需要根据自身情况来选择的。如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池是,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题。

  • newFixedThreadPool 可以通过传入一个int参数来指定创建一个定长的线程池,该线程池的核心线程数和最大线程数都是你传进去的参数的值,存活时间都为0说明只要任务空闲下来了,就会被销毁,阻塞队列的最大值为MAX_VALUE。所以他的缺点是,可能会将大量的时间花在处理堆积的请求阻塞队列中的线程。
  public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                   0L, TimeUnit.MILLISECONDS,
                   new LinkedBlockingQueue<Runnable>());
  }
public class FixedPoolDemo {
  public static void main(String[] args) {
    ExecutorService pool = Executors.newFixedThreadPool(10);
//    ExecutorService pool = Executors.newFixedThreadPool(2);
    for (int i = 0; i < 1000; i++) {
      int finalI = i;
      pool.execute(() -> {
        System.out.println(Thread.currentThread().getName()+"----"+ finalI);
      });
    }
  }
}

从运行结果可以看出,线程池一直都是维持着十个线程

.....
pool-1-thread-5----882
pool-1-thread-1----881
pool-1-thread-4----865
pool-1-thread-10----989
pool-1-thread-3----931
pool-1-thread-2----934
pool-1-thread-9----910
pool-1-thread-6----896

  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
       new DelayedWorkQueue());
  }

以上四种线程池,各有优劣点

newFixedThreadPool、newSingleThreadExecutor:

主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

newCachedThreadPool、newScheduledThreadPool:

主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

阿里线程池规范

  1. 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
  2. FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
  3. CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

总结

本篇文章首先我们知道了线程池有什么好处,然后了解一些线程的执行方法,submit,execute,shutdown以及他们的区别,用法等等,然后对几种线程池做了一个大概的介绍,以及他们的作用,好处和弊端。如果看的细心的同学可以看代码发现,这些线程池其实本质上都是通过创建一个 ThreadPoolExecutor ,包括阿里的线程池规范也是建议用ThreadPoolExecutor ,但是本篇文章只是对线程池的作用以及分类做一个概述,在下篇文章中,将会详细的讲一下ThreadPoolExecutor

以上就是JAVA线程池专题(概念和作用)的详细内容,更多关于Java线程池的概念和作用的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java手动配置线程池过程详解

    线程池中,常见有涉及到的: ExecutorService executorService = Executors.newSingleThreadExecutor(); ExecutorService executorService1 = Executors.newCachedThreadPool(); ExecutorService executorService2 = Executors.newFixedThreadPool(3); 关于Executors和ExecutorService从记

  • java 线程池的实现方法

    线程池有以下几种实现方式: Executors目前提供了5种不同的线程池创建配置: 1.newCachedThreadPool() 它是用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程:如果线程闲置时间超过60秒,则被终止并移除缓存:长时间闲置时,这种线程池,不会消耗什么资源.其内部使用SynchronousQueue作为工作队列. 2.newFixedThreadPool(int nThreads) 重用指定数目(nThre

  • Java线程池用法实战案例分析

    本文实例讲述了Java线程池用法.分享给大家供大家参考,具体如下: 一 使用newSingleThreadExecutor创建一个只包含一个线程的线程池 1 代码 import java.util.concurrent.*; public class executorDemo { public static void main( String[] args ) { ExecutorService executor = Executors.newSingleThreadExecutor(); ex

  • Java 线程状态和等待唤醒机制和线程池的实现

    1.概念 线程一共有6中状态,相互之间可以互相转换. 等待唤醒案例(线程之间的通信) 实现: 等待唤醒案例:线程之间的通信 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子 注意: 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行 同步使用的锁对象必须保证唯一 只有锁对象才能调用wait和noti

  • Java线程池ThreadPoolExecutor原理及使用实例

    引导 要求:线程资源必须通过线程池提供,不允许在应用自行显式创建线程: 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题.如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗内存或者"过度切换"的问题. 线程池介绍线程池概述   线程池,顾名思义是一个放着线程的池子,这个池子的线程主要是用来执行任务的.当用户提交任务时,线程池会创建线程去执行任务,若任务超过了核心线程数的时候,会在一个任务队列里进行排队等待,这个详细流程,我们会后面细

  • Java判断线程池线程是否执行完毕

    在使用多线程的时候有时候我们会使用 java.util.concurrent.Executors的线程池,当多个线程异步执行的时候,我们往往不好判断是否线程池中所有的子线程都已经执行完毕,但有时候这种判断却很有用,例如我有个方法的功能是往一个文件异步地写入内容,我需要在所有的子线程写入完毕后在文件末尾写"---END---"及关闭文件流等,这个时候我就需要某个标志位可以告诉我是否线程池中所有的子线程都已经执行完毕,我使用这种方式来判断. public class MySemaphore

  • Java线程池ForkJoinPool实例解析

    这篇文章主要介绍了Java线程池ForkJoinPool实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 背景:ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个"小任务",把多个"小任务"放到多个处理器核心上并行执行:当多个"小任务"执行完成之后,再将这些执行结果合并起来即可.这种思想值得学习. import java.io.IOExcept

  • Java线程池的应用实例分析

    本文实例讲述了Java线程池的应用.分享给大家供大家参考,具体如下: 一 使用Future与Callable来计算斐波那契数列 1 代码 import java.util.concurrent.*; public class FutureCallableDemo { static long fibonacci(long n) { if (n == 1 ||n == 2) return 1; else return fibonacci(n - 1) + fibonacci(n - 2); } pu

  • 到底如何设置Java线程池的大小的方法示例

    在我们日常业务开发过程中,或多或少都会用到并发的功能.那么在用到并发功能的过程中,就肯定会碰到下面这个问题 并发线程池到底设置多大呢? 通常有点年纪的程序员或许都听说这样一个说法 (其中 N 代表 CPU 的个数) CPU 密集型应用,线程池大小设置为 N + 1 IO 密集型应用,线程池大小设置为 2N 这个说法到底是不是正确的呢? 其实这是极不正确的.那为什么呢? 首先我们从反面来看,假设这个说法是成立的,那我们在一台服务器上部署多少个服务都无所谓了.因为线程池的大小只能服务器的核数有关,所

  • JAVA线程池专题(概念和作用)

    线程池的作用 我们在用一个东西的时候,首先得搞明白一个问题.这玩意是干嘛的,为啥要用这个,用别的不行吗.那么一个一个解决这些问题 我们之前都用过数据库连接池,线程池的作用和连接池有点类似,频繁的创建,销毁线程会造成大量的不必要的性能开销,所以这个时候就出现了一个东西统一的管理线程,去负责线程啥时候销毁,啥时候创建,以及维持线程的状态,当程序需要使用线程的时候,直接从线程池拿,当程序用完了之后,直接把线程放回线程池,不需要去管线程的生命周期,专心的执行业务代码就行. 当然,如果非要是自己想手动ne

  • Java 线程池的作用以及该如何使用

    服务端应用程序(如数据库和 Web 服务器)需要处理来自客户端的高并发.耗时较短的请求任务,所以频繁的创建处理这些请求的所需要的线程就是一个非常消耗资源的操作.常规的方法是针对一个新的请求创建一个新线程,虽然这种方法似乎易于实现,但它有重大缺点.为每个请求创建新线程将花费更多的时间,在创建和销毁线程时花费更多的系统资源.因此同时创建太多线程的 JVM 可能会导致系统内存不足,这就需要限制要创建的线程数,也就是需要使用到线程池. 一.什么是 Java 中的线程池? 线程池技术就是线程的重用技术,使

  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

  • 详解Java线程池是如何重复利用空闲线程的

    在Java开发中,经常需要创建线程去执行一些任务,实现起来也非常方便,但如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间.此时,我们很自然会想到使用线程池来解决这个问题. 使用线程池的好处: 降低资源消耗.java中所有的池化技术都有一个好处,就是通过复用池中的对象,降低系统资源消耗.设想一下如果我们有n多个子任务需要执行,如果我们为每个子任务都创建一个执行线程,而创建线程的过程是需要一定的系统消耗

  • Java线程池大小的设置方法实例

    目录 Java 中线程池创建的几种方式

  • java线程池详解及代码介绍

    目录 一.线程池简介 二.四种常见的线程池详解 三.缓冲队列BlockingQueue和自定义线程池ThreadPoolExecutor 总结 一.线程池简介 线程池的概念 线程池就是首先创建一些线程,它们的集合称为线程池,使用线程池可以很好的提高性能,线程池在系统启动时既创建大量空闲的线程,程序将一个任务传给线程池.线程池就会启动一条线程来执行这个任务,执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务. 线程池的工作机制 在线程池的编程模式下,任务是提交给整个

  • 超详细讲解Java线程池

    目录 池化技术 池化思想介绍 池化技术的应用 如何设计一个线程池 Java线程池解析 ThreadPoolExecutor使用介绍 内置线程池使用 ThreadPoolExecutor解析 整体设计 线程池生命周期 任务管理解析 woker对象 Java线程池实践建议 不建议使用Exectuors 线程池大小设置 线程池监控 带着问题阅读 1.什么是池化,池化能带来什么好处 2.如何设计一个资源池 3.Java的线程池如何使用,Java提供了哪些内置线程池 4.线程池使用有哪些注意事项 池化技术

  • 详解Java线程池如何实现优雅退出

    目录 shutdown()方法 shutdownNow()方法 awaitTermination(long, TimeUnit)方法 在[高并发专题]中,我们从源码角度深度分析了线程池中那些重要的接口和抽象类.深度解析了线程池是如何创建的,ThreadPoolExecutor类有哪些属性和内部类,以及它们对线程池的重要作用.深度分析了线程池的整体核心流程,以及如何拆解Worker线程的执行代码,深度解析Worker线程的执行流程. 本文,我们就来从源码角度深度解析线程池是如何优雅的退出程序的.首

  • Java线程池使用与原理详解

    线程池是什么? 我们可以利用java很容易创建一个新线程,同时操作系统创建一个线程也是一笔不小的开销.所以基于线程的复用,就提出了线程池的概念,我们使用线程池创建出若干个线程,执行完一个任务后,该线程会存在一段时间(用户可以设定空闲线程的存活时间,后面会介绍),等到新任务来的时候就直接复用这个空闲线程,这样就省去了创建.销毁线程损耗.当然空闲线程也会是一种资源的浪费(所有才有空闲线程存活时间的限制),但总比频繁的创建销毁线程好太多. 下面是我的测试代码 /* * @TODO 线程池测试 */ @

  • Java 线程池详解及实例代码

    线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁.如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因. 例如Android中常见到的很多通用组件一般都离不开"池"的概念,如各种图片

随机推荐