【Java并发编程】Java中Executor框架和线程池的介绍与使用

本文深入介绍了Java中的Executor框架和线程池的使用方法,探讨了为何引入Executor框架以解决传统newThread()方式的问题,详细解释了Executor框架的两级调度模型,并提供了四种线程池的具体创建与使用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java中Executor框架和线程池的介绍与使用


首先声明该内容是基于Java 8 进行介绍和讲解


前提概念


什么是Executor框架?

在Java 5之后,并发编程引入了一堆新的启动、调度和管理线程的API,而这些API的整体框架就是Executor框架

  • Executor的内部实现主要就是线程池来实现,既Executor是通过线程池的方式来控制上层的调度的。所以Executor一定角度上扮演者线程工厂的角色,我们可以通过Executor框架创建特定功能的线程池。

为什么引入Executor框架?

为什么要引入Executor框架呢?那我们就需要来讨论一下new Thread()的缺点:

  • 每次new Thread()和destroy一个Thread都会耗费一定的系统资源 ,比如我们为每个任务都创建一个新线程来执行,这就会消耗大量的CPU资源来创建和消耗线程,同时这种策略也会使处于高负荷状态的应用崩溃
  • 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。
  • 同时new Thread()的方式不利于扩展,比如如定时执行、定期执行、线程中断

总之就是,无人管理,不利于拓展,耗费资源,所以为了解决这一个问题,JDK1.5开始引入了Executor框架用于管理线程和调度线程,可以参考Executor框架的两级调度模型


Executor框架的两级调度模型

在HotSpot 虚拟机线程模型中,Java线程(java.lang.Thread)被一对一的映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程。但该Java线程终止时,这个本地操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU.

而有了Executor框架之后,我们可以将线程调度分为上下两层:

  • 上层是Java层面上的应用线程层
  • 下层是本地操作系统层面上的底层线程层

在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在下层,操作系统内核将这些线程映射到硬件处理器上。如下图:

从图中(图中下面是CPU,非任务,画错了,Sorry),我们看出,应用程序通过Executor框架来控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。


Executor框架的组织架构图

Executor框架的组织架构图如下:

从架构图,我们可以看出:

  • Executor是一个顶级的接口,接口只有execute一个方法
  • ExecutorService是Executor的子接口,拥有更多需要用的的方法规范
  • ExecutorService拥有两个重要实现类ThreadPoolExecutor和ScheuledThreadPoolExecutor
  • ScheuledThreadPoolExecutor实际最后也是通过ThreadPoolExecutor去实现的

Executor框架的主要成员有:

  • ThreadPoolExecutor
  • ScheduledThreadPoolExecutor
  • Future接口
  • Runnable接口
  • Callable接口
  • Executors工具类

Executors工具类创建的4种线程池


从上面的概念中,我们知道了Executor框架实际是依靠线程池来控制上层的线程调度的。所以线程池自然是重点。而Executor的线程池一般都是通过Executors工具类来创建的,大致可以分为4种:

  • FixedThreadPool
  • SingleThreadPool
  • CacheThreadPool
  • ScheduleThreadPoll

而线程池又可以根据Executor大致分为两种:

  • ThreadPoolExcutor(FixedThreadPool,SingleThreadPool,CacheThreadPool)
  • ScheduleThreadExcutor(ScheduleThreadPool)

FixedThreadPool

Executors的方法:

 public static ExecutorService newFixedThreadPool(int nThreads) 
 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 

使用例子:

 ExecutorService threadPool = Executors.newFixedThreadPool(5);

FixedThreadPool的特点:

  • FixedThreadPool线程池是一个线程数固定的线程池,比如Executors.newFixedThreadPool(5)的意思就是创建一个线程数固定为5个的线程池。
  • FixedThreadPool线程池适用于为满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器
  • 使用链式有界阻塞列队LinkedBlockingQueue来存放任务,既当工作线程已满,没有空闲线程时,待执行任务全部都放在一个LinkedBlockingQueue队列中,这是一个有界的阻塞队列,如果队列已满,生成任务的生成者将被阻塞

SingleThreadPool

Executors的创建方法:

public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

使用例子:

ExecutorService threadPool = Executors.newSingleThreadPool(5);

SingleThreadPool的特点:

  • SingleThreadPool是一个单线程的线程池,既整个线程池中,仅有一个线程
  • SingleThreadPool适用于需要保证顺序地执行的各个任务且在任意时间点,不会有多个线程任务是活动的应用场景
  • SIngleThreadPool使用的是LinkedBlockQueue链式有界阻塞队列来存放任务。如果队列已满,生成任务的生成者将被阻塞

CacheThreadPool

Executors的创建方法:

public static ExecutorService newCachedThreadPool()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

使用例子:

ExecutorService threadPool = Executors.newSingleThreadPool(5);

CacheThreadPool的特点:

  • CacheThreadPool是一个大小无界的线程池,既是一个按需创建线程的线程池
  • 适用于执行很多的短期异步任务的小程序或负载较轻的服务器
  • 使用SynchronousQueue不存放元素的阻塞队列来存放任务

ScheduleThreadPool

Executors的创建方法:

//01
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

//02
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)

使用例子:

//01
ExecutorService threadPool = Executors.newScheduledThreadPool(5);
//02
ExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();

ScheduleThreadPool有两种形式:

  • 第一种是限定某个数量的线程池
  • 另一种是单线程线程池

ScheduleThreadPool的特点:

  • ScheduleThreadPool有两种形式,一种是类似于FixedThreadPool的限制线程数量的线程池,另一种是类似于SingleThreadPool的单线程线程池
  • ScheduleThreadPool是属于ScheduleThreadPoolExecutor的,其他的3种是ThreadPoolExecutor
  • ScheduleThreadPool是使用DelayedWorkQueue来存储任务的

小结:

  • 虽然可以使用Executors工具类直接生产线程池,但是就像阿里巴巴规范所说的一样,我们不推荐Executors去生成线程池,而是通过new ThreadPoolExcutor()的方式。因为使用工具的线程池参数固定,不灵活,有一定的缺陷,为了规范,都统一使用new ThreadPoolExcutor()的方式,按需填写参数。

线程池的创建和使用


线程池的创建
new ThreadPoolExecutor(corePoolSize,maxinumPoolSize,keepAliveTime,milliseconds,
runnableTaskQueue,handle)

以上是创建一个线程的方法,new一个ThreadPoolExecutor,其中参数有6个:

  • corePoolSize
    线程池的基本大小,既该线程池初始化时拥有多少个存活线程。既比如我的核心线程池有5个线程。那个我任务只有2个,线程池也是存活5个线程。
  • maxinumPoolSize
    线程池的最大数量,线程池允许线程的最大数量。但如果使用了无界的阻塞队列,则这个参数就没有什么效果
  • keepAliveTime
    线程活动保持时间,线程池的工作线程空闲后,保持存活的时间,所以,如果任务很多,每个任务的执行时间比较短,可以调大时间,提高线程的利用率。
  • milliseconds
    线程活动保持时间的单位
  • runnableTaskQueue
    该线程池的存放任务的阻塞队列是那种
  • handle
    饱和策略,当队列和线程池都满了,说明线程池处于饱和状态,那么必须采用一种策略来处理新提交的任务。
JDK1.5的4种饱和策略
  • AbortPolicy
    如果队列满了,则直接抛出异常。是Executors生成线程池的默认方法
  • CallerRunsPolicy
    如果队列满了,则使用调用线程执行溢出的任务,即不抛弃任何一个任务
  • DiscardOldesPolicy
    丢弃队列中最老的的任务,插入新任务
  • DiscardPlicy
    如果队列满了,则丢弃所有新任务,直到队列有位置
向线程池提交任务

大致有两个方法

  • execute()
  • submit()

参数是任务,execute()用来提交没有返回值的任务,submit()方法用来提交带返回值的任务

关闭线程池
  • shutdown
  • shutdownNow

shutdown仅仅是设置运行标识为SHUTDOWN,正在运行的线程等待运行结束
shutdownNow则是设置运行标识为STOP,正在运行的线程也会中断掉

实现例子
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                8,
                8,
                1,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(16),
                new ThreadPoolExecutor.CallerRunsPolicy());

参考资料


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值