10分钟带你徒手做个Java线程池

摘要:花10分钟开发一个极简版的Java线程池,让小伙伴们更好的理解线程池的核心原理。

本文分享自华为云社区《放大招了,冰河带你10分钟手撸Java线程池,yyds,赶快收藏吧》,作者:冰 河。

Java线程池核心原理

看过Java线程池源码的小伙伴都知道,在Java线程池中最核心的类就是ThreadPoolExecutor,而在ThreadPoolExecutor类中最核心的构造方法就是带有7个参数的构造方法,如下所示。

 public ThreadPoolExecutor(int corePoolSize,
 int maximumPoolSize,
 long keepAliveTime,
 TimeUnit unit,
 BlockingQueue<Runnable> workQueue,
 ThreadFactory threadFactory,
 RejectedExecutionHandler handler)

各参数的含义如下所示。

  • corePoolSize:线程池中的常驻核心线程数。
  • maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1。
  • keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。
  • unit:keepAliveTime的单位。
  • workQueue:任务队列,被提交但尚未被执行的任务。
  • threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可。
  • handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示数(maxnumPoolSize)时,如何来拒绝请求执行的runnable的策略。

并且Java的线程池是通过 生产者-消费者模式 实现的,线程池的使用方是生产者,而线程池本身就是消费者。

Java线程池的核心工作流程如下图所示。

10分钟带你徒手做个Java线程池

手撸Java线程池

我们自己手动实现的线程池要比Java自身的线程池简单的多,我们去掉了各种复杂的处理方式,只保留了最核心的原理:线程池的使用者向任务队列中添加任务,而线程池本身从任务队列中消费任务并执行任务。

10分钟带你徒手做个Java线程池

只要理解了这个核心原理,接下来的代码就简单多了。在实现这个简单的线程池时,我们可以将整个实现过程进行拆解。拆解后的实现流程为:定义核心字段、创建内部类WorkThread、创建ThreadPool类的构造方法和创建执行任务的方法。

10分钟带你徒手做个Java线程池

定义核心字段

首先,我们创建一个名称为ThreadPool的Java类,并在这个类中定义如下核心字段。

  • DEFAULT_WORKQUEUE_SIZE:静态常量,表示默认的阻塞队列大小。
  • workQueue:模拟实际的线程池使用阻塞队列来实现生产者-消费者模式。
  • workThreads:模拟实际的线程池使用List集合保存线程池内部的工作线程。

核心代码如下所示。

//默认阻塞队列大小
private static final int DEFAULT_WORKQUEUE_SIZE = 5;
//模拟实际的线程池使用阻塞队列来实现生产者-消费者模式
private BlockingQueue<Runnable> workQueue;
//模拟实际的线程池使用List集合保存线程池内部的工作线程
private List<WorkThread> workThreads = new ArrayList<WorkThread>();

创建内部类WordThread

在ThreadPool类中创建一个内部类WorkThread,模拟线程池中的工作线程。主要的作用就是消费workQueue中的任务,并执行任务。由于工作线程需要不断从workQueue中获取任务,所以,这里使用了while(true)循环不断尝试消费队列中的任务。

核心代码如下所示。

//内部类WorkThread,模拟线程池中的工作线程
//主要的作用就是消费workQueue中的任务,并执行
//由于工作线程需要不断从workQueue中获取任务,使用了while(true)循环不断尝试消费队列中的任务
class WorkThread extends Thread{
 @Override
 public void run() {
 //不断循环获取队列中的任务
 while (true){
 //当没有任务时,会阻塞
 try {
 Runnable workTask = workQueue.take();
 workTask.run();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
}

创建ThreadPool类的构造方法

这里,我们为ThreadPool类创建两个构造方法,一个构造方法中传入线程池的容量大小和阻塞队列,另一个构造方法中只传入线程池的容量大小。

核心代码如下所示。

//在ThreadPool的构造方法中传入线程池的大小和阻塞队列
public ThreadPool(int poolSize, BlockingQueue<Runnable> workQueue){
 this.workQueue = workQueue;
 //创建poolSize个工作线程并将其加入到workThreads集合中
 IntStream.range(0, poolSize).forEach((i) -> {
 WorkThread workThread = new WorkThread();
 workThread.start();
 workThreads.add(workThread);
 });
}
//在ThreadPool的构造方法中传入线程池的大小
public ThreadPool(int poolSize){
 this(poolSize, new LinkedBlockingQueue<>(DEFAULT_WORKQUEUE_SIZE));
}

创建执行任务的方法

在ThreadPool类中创建执行任务的方法execute(),execute()方法的实现比较简单,就是将方法接收到的Runnable任务加入到workQueue队列中。

核心代码如下所示。

//通过线程池执行任务
public void execute(Runnable task){
 try {
 workQueue.put(task);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
}

完整源码

这里,我们给出手动实现的ThreadPool线程池的完整源代码,如下所示。

package io.binghe.thread.pool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.IntStream;
/**
 * @author binghe
 * @version 1.0.0
 * @description 自定义线程池
 */
public class ThreadPool {
 //默认阻塞队列大小
 private static final int DEFAULT_WORKQUEUE_SIZE = 5;
 //模拟实际的线程池使用阻塞队列来实现生产者-消费者模式
 private BlockingQueue<Runnable> workQueue;
 //模拟实际的线程池使用List集合保存线程池内部的工作线程
 private List<WorkThread> workThreads = new ArrayList<WorkThread>();
 //在ThreadPool的构造方法中传入线程池的大小和阻塞队列
 public ThreadPool(int poolSize, BlockingQueue<Runnable> workQueue){
 this.workQueue = workQueue;
 //创建poolSize个工作线程并将其加入到workThreads集合中
 IntStream.range(0, poolSize).forEach((i) -> {
 WorkThread workThread = new WorkThread();
 workThread.start();
 workThreads.add(workThread);
 });
 }
 //在ThreadPool的构造方法中传入线程池的大小
 public ThreadPool(int poolSize){
 this(poolSize, new LinkedBlockingQueue<>(DEFAULT_WORKQUEUE_SIZE));
 }
 //通过线程池执行任务
 public void execute(Runnable task){
 try {
 workQueue.put(task);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 //内部类WorkThread,模拟线程池中的工作线程
 //主要的作用就是消费workQueue中的任务,并执行
 //由于工作线程需要不断从workQueue中获取任务,使用了while(true)循环不断尝试消费队列中的任务
 class WorkThread extends Thread{
 @Override
 public void run() {
 //不断循环获取队列中的任务
 while (true){
 //当没有任务时,会阻塞
 try {
 Runnable workTask = workQueue.take();
 workTask.run();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
 }
}

没错,我们仅仅用了几十行Java代码就实现了一个极简版的Java线程池,没错,这个极简版的Java线程池的代码却体现了Java线程池的核心原理。

接下来,我们测试下这个极简版的Java线程池。

编写测试程序

测试程序也比较简单,就是通过在main()方法中调用ThreadPool类的构造方法,传入线程池的大小,创建一个ThreadPool类的实例,然后循环10次调用ThreadPool类的execute()方法,向线程池中提交的任务为:打印当前线程的名称--->> Hello ThreadPool。

整体测试代码如下所示。

package io.binghe.thread.pool.test;
import io.binghe.thread.pool.ThreadPool;
import java.util.stream.IntStream;
/**
 * @author binghe
 * @version 1.0.0
 * @description 测试自定义线程池
 */
public class ThreadPoolTest {
 public static void main(String[] args){
 ThreadPool threadPool = new ThreadPool(10);
 IntStream.range(0, 10).forEach((i) -> {
 threadPool.execute(() -> {
 System.out.println(Thread.currentThread().getName() + "--->> Hello ThreadPool");
 });
 });
 }
}

接下来,运行ThreadPoolTest类的main()方法,会输出如下信息。

Thread-0--->> Hello ThreadPool
Thread-9--->> Hello ThreadPool
Thread-5--->> Hello ThreadPool
Thread-8--->> Hello ThreadPool
Thread-4--->> Hello ThreadPool
Thread-1--->> Hello ThreadPool
Thread-2--->> Hello ThreadPool
Thread-5--->> Hello ThreadPool
Thread-9--->> Hello ThreadPool
Thread-0--->> Hello ThreadPool

至此,我们自定义的Java线程池就开发完成了。

总结

线程池的核心原理其实并不复杂,只要我们耐心的分析,深入其源码理解线程池的核心本质,你就会发现线程池的设计原来是如此的优雅。希望通过这个手写线程池的小例子,能够让你更好的理解线程池的核心原理。

 

点击关注,第一时间了解华为云新鲜技术~

原文链接:https://www.cnblogs.com/huaweiyun/p/17333748.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:10分钟带你徒手做个Java线程池 - Python技术站

(0)
上一篇 2023年4月19日
下一篇 2023年4月20日

相关文章

  • 完整java开发中JDBC连接数据库代码和步骤

    当进行Java开发中需要连接数据库进行数据操作时,我们可以使用JDBC来完成这个任务。下面详细介绍完整的JDBC连接数据库代码和步骤,这里以MySQL数据库和Oracle数据库为例。 JDBC连接MySQL数据库 步骤一:导入JDBC驱动 要连接MySQL数据库,我们需要使用MySQL JDBC驱动程序。将JDBC驱动程序的JAR文件添加到classpath…

    Java 2023年5月19日
    00
  • Resty极简restful框架快速接入Spring

    Resty是一个基于Java的类似于JAX-RS的RESTful框架,它提供了核心的RESTful应用层框架,可以快速搭建RESTful应用程序。在这里,我将详细介绍如何将Resty快速整合到Spring框架中。 准备工作 在整合Resty之前,请确认已经安装好以下环境: JDK 1.8或更高版本 Maven 3.0或更高版本 Spring框架 添加Mave…

    Java 2023年5月19日
    00
  • Java学习之线程同步与线程间通信详解

    Java学习之线程同步与线程间通信详解 为什么需要线程同步和线程间通信 在多线程编程中,由于多个线程可能同时执行同一任务,可能会导致竞态条件(Race Condition)的出现,即数据被多个线程同时修改,从而导致程序运行出错。为了避免这种情况,需要通过线程同步机制来协调多个线程的共同操作。 而线程间通信则是线程同步机制的一种实现方式,它可以让线程之间传递消…

    Java 2023年5月18日
    00
  • 解决idea2020.1找不到程序包和符号的问题

    问题背景: 在使用IntelliJ IDEA 2020.1时,有时会遇到找不到程序包和符号的问题。这个问题可能是由于项目依赖导致的,也可能是由于代码中的语法错误导致的。 解决方案: 检查项目依赖 首先,需要检查项目的依赖是否正确。在项目的pom.xml文件(Maven项目)或build.gradle文件(Gradle项目)中查看所依赖的库是否正确且版本是否匹…

    Java 2023年5月20日
    00
  • myeclipse开发servlet_动力节点Java学院整理

    MyEclipse开发Servlet攻略 简介 本文主要介绍如何使用MyEclipse开发Servlet,并提供一些示例说明和代码模板。MyEclipse集成了Tomcat服务器,使得我们可以直接在MyEclipse中创建和运行Servlet。 步骤 第一步:创建项目 在MyEclipse中创建一个新项目,选择Web Project,并勾选Generate …

    Java 2023年6月15日
    00
  • BootStrap在jsp中的使用

    下面是 “Bootstrap在JSP中的使用” 的完整攻略: 简介 Bootstrap是一个流行的前端开发框架,被广泛应用于Web开发。在JSP中使用Bootstrap可以非常方便地快速构建漂亮且易于维护的界面。本攻略将介绍Bootstrap在JSP中的使用。 步骤 以下是在JSP中使用Bootstrap的步骤: 步骤1:下载Bootstrap并添加到JSP…

    Java 2023年6月15日
    00
  • Java实现字符数组全排列的方法

    下面是Java实现字符数组全排列的方法的完整攻略: 步骤1:定义一个递归函数 首先,我们需要使用递归来实现字符数组的全排列。定义一个递归函数,函数的参数包括要排列的字符数组arr、开始交换的索引位置start以及结束的索引位置end。 public static void permutation(char[] arr, int start, int end)…

    Java 2023年5月26日
    00
  • Java遍历输出指定目录、树形结构所有文件包括子目录下的文件

    Java遍历输出指定目录、树形结构所有文件包括子目录下的文件 在Java中,我们可以使用递归来遍历指定目录下的所有文件及其子目录下的文件,并对它们进行操作,例如输出它们的名称、大小等信息。下面是具体的步骤: 创建一个方法,用于遍历指定目录及其子目录下所有文件。方法需要传入一个文件对象作为参数,此文件对象表示需要遍历的目录。方法会先输出当前目录及其子目录下的所…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部