Java实现手写乞丐版线程池的示例代码

目录
  • 前言
  • 线程池的具体实现
    • 线程池实现思路
    • 线程池实现代码
    • 线程池测试代码
  • 杂谈
  • 总结

前言

在上篇文章线程池的前世今生当中我们介绍了实现线程池的原理,在这篇文章当中我们主要介绍实现一个非常简易版的线程池,深入的去理解其中的原理,麻雀虽小,五脏俱全。

线程池的具体实现

线程池实现思路

任务保存到哪里?

在上篇文章线程池的前世今生当中我们具体去介绍了线程池当中的原理。在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用execute方法将任务添加到线程池当中)里面去拿任务然后执行,现在需要思考我们应该用什么去实现任务池呢?

答案是阻塞队列,因为我们需要保证在多个线程往任务池里面加入任务的时候并发安全,JDK已经给我们提供了这样的数据结构——BlockingQueue,这个是一个并发安全的阻塞队列,他之所以叫做阻塞队列,是因为我们可以设置队列当中可以容纳数据的个数,当加入到队列当中的数据超过这个值的时候,试图将数据加入到阻塞队列当中的线程就会被挂起。当队列当中为空的时候,试图从队列当中取出数据的线程也会被挂起。

线程的设计

在我们自己实现的线程池当中我们定一个Worker类去不断的从任务池当中取出任务,然后进行执行。在我们自己定义的worker当中还需要有一个变量isStopped表示线程是否停止工作。同时在worker当中还需要保存当前是哪个线程在执行任务,因此在我们自己设计的woker类当中还需要有一个thisThread变量,保存正在执行任务的线程,因此worker的整体设计如下:

package cscore.concurrent.java.threadpool;

import java.util.concurrent.BlockingQueue;

public class Worker implements Runnable {

  private Thread thisThread; // 表示正在执行任务的线程
  private BlockingQueue<Runnable> taskQueue; // 由线程池传递过来的任务队列
  private volatile boolean isStopped; // 表示 worker 是否停止工作 需要使用 volatile 保证线程之间的可见性

  public Worker(BlockingQueue taskQueue) { // 这个构造方法是在线程池的实现当中会被调用
    this.taskQueue = taskQueue;
  }

  // 线程执行的函数
  @Override
  public void run() {
    thisThread = Thread.currentThread(); // 获取执行任务的线程
    while (!isStopped) { // 当线程没有停止的时候就不断的去任务池当中取出任务
      try {
        Runnable task = taskQueue.take(); // 从任务池当中取出任务 当没有任务的时候线程会被这个方法阻塞
        task.run(); // 执行任务 任务就是一个 Runnable 对象
      } catch (InterruptedException e) {
        // do nothing
        // 这个地方很重要 你有没有思考过一个问题当任务池当中没有任务的时候 线程会被阻塞在 take 方法上
        // 如果我们后面没有任务提交拿他就会一直阻塞 那么我们该如何唤醒他呢
        // 答案就在下面的函数当中 调用线程的 interruput 方法 那么take方法就会产生一个异常 然后我们
        // 捕获到一异常 然后线程退出
      }
    }
  }

  public synchronized void stopWorker() {
    if (isStopped) {
      throw new RuntimeException("thread has been interrupted");
    }
    isStopped = true;
    thisThread.interrupt(); // 中断线程产生异常
  }

  public synchronized boolean isStopped() {
    return isStopped;
  }
}

线程池的参数

在我们自己实现的线程池当中,我们只需要定义两个参数一个是线程的个数,另外一个是阻塞队列(任务池)当中最大的任务个数。在我们自己实现的线程池当中还需要有一个变量isStopped表示线程池是否停止工作了,因此线程池的初步设计大致如下:

  private BlockingQueue taskQueue; // 任务池
  private volatile boolean isStopped; //
  private final List<Worker> workers = new ArrayList<>();// 保存所所有的执行任务的线程

  public ThreadPool(int numThreads, int maxTasks) {
    this.taskQueue = new ArrayBlockingQueue(maxTasks);
    for (int i = 0; i < numThreads; i++) {
      workers.add(new Worker(this.taskQueue));
    }
    int i = 1;
    // 这里产生线程 然后启动线程
    for (Worker worker : workers) {
      new Thread(worker, "ThreadPool-" + i + "-thread").start();
      i++;
    }
  }

线程池实现代码

在上文当中我们大致设计的线程池的初步结构,从上面的结果可以看出当我们造一个ThreadPool对象的时候会产生指定线程的数目线程并且启动他们去执行任务,现在我们还需要设计的就是如果关闭线程!我们在关闭线程的时候还需要保证所有的任务都被执行完成然后才关闭所有的线程,再退出,我们设计这个方法为shutDown。除此之外我们还设计一个函数可以强制退出,不用执行所有的任务了,就直接退出,这个方法为stop。整个线程池实现的代码如下:

package cscore.concurrent.java.threadpool;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ThreadPool {

  private BlockingQueue taskQueue;
  private volatile boolean isStopped;
  private final List<Worker> workers = new ArrayList<>();

  public ThreadPool(int numThreads, int maxTasks) {
    this.taskQueue = new ArrayBlockingQueue(maxTasks);
    for (int i = 0; i < numThreads; i++) {
      workers.add(new Worker(this.taskQueue));
    }
    int i = 1;
    for (Worker worker : workers) {
      new Thread(worker, "ThreadPool-" + i + "-thread").start();
      i++;
    }
  }

  // 下面这个方法是向线程池提交任务
  public void execute(Runnable runnable) throws InterruptedException {
    if (isStopped) {
      // 如果线程池已经停下来了,就不在向任务队列当中提交任务了
      System.err.println("thread pool has been stopped, so quit submitting task");
      return;
    }
    taskQueue.put(runnable);
  }

  // 强制关闭线程池
  public synchronized void stop() {
    isStopped = true;
    for (Worker worker : workers) {
      worker.stopWorker();
    }
  }

  public synchronized void shutDown() {
    // 先表示关闭线程池 线程就不能再向线程池提交任务
    isStopped = true;
    // 先等待所有的任务执行完成再关闭线程池
    waitForAllTasks();
    stop();
  }

  private void waitForAllTasks() {
    // 当线程池当中还有任务的时候 就不退出循环
    while (taskQueue.size() > 0)
      Thread.yield();
  }
}

线程池测试代码

package cscore.concurrent.java.threadpool;

public class TestPool {

  public static void main(String[] args) throws InterruptedException {
    ThreadPool pool = new ThreadPool(3, 1024);

    for (int i = 0; i < 10; i++) {
      int tmp = i;
      pool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + " say hello " + tmp);
      });
    }
    pool.shutDown();
  }
}

上面的代码输出结果:

ThreadPool-2-thread say hello 1
ThreadPool-2-thread say hello 3
ThreadPool-2-thread say hello 4
ThreadPool-2-thread say hello 5
ThreadPool-2-thread say hello 6
ThreadPool-2-thread say hello 7
ThreadPool-2-thread say hello 8
ThreadPool-2-thread say hello 9
ThreadPool-3-thread say hello 2
ThreadPool-1-thread say hello 0

从上面的结果来看确实实现了线程池的效果。

杂谈

可能你会有疑问,当我们调用 interrupt的时候是如何产生异常的,我们仔细看一个阻塞队列的实现。在ArrayBlockingQueue当中take方法实现如下:

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

在这个方法当中调用的是锁的lock.lockInterruptibly();方法,当调用这个方法的时候线程是可以被interrupt方法中断的,然后会抛出InterruptedException异常。

总结

在本篇文章当中我们主要实现了一个乞丐版的线程池,这个线程池离JDK给我们提供的线程池还是有一点距离,JDK给我们提供给的线程池还有很多其他的参数,我们将在后续的几篇文章当中继续向JDK给我们提供的线程池靠近,直至实现一个盗版的JDK的线程池。本篇文章的代码在下面的链接当中也可以访问。

到此这篇关于Java实现手写乞丐版线程池的示例代码的文章就介绍到这了,更多相关Java线程池内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

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

  • Java如何自定义线程池中队列

    目录 背景 问题分析 问题解决 总结 两个队列的UML关系图 SynchronousQueue的定义 ArrayBlockingQueue的定义 分析 jdk源码中关于线程池队列的说明 背景 业务交互的过程中涉及到了很多关于SFTP下载的问题,因此在代码中定义了一些线程池,使用中发现了一些问题, 代码类似如下所示: public class ExecutorTest { private static ExecutorService es = new ThreadPoolExecutor(2, 1

  • Java如何手动创建线程池

    目录 如何手动创建线程池 构造器 队列 饱和策略 示例 源码分析 线程池工具类 实现线程的三种方式 使用ThreadPoolExecutor编写线程池工具类 如何手动创建线程池 jdk提供了一个通过ThreadPoolExecutor创建一个线程池的类 构造器 使用给定的参数和默认的饱和策略.默认的工厂方法创建线程池 ThreadPoolExecutor(int corePoolSize,  int maximumPoolSize,  long keepAliveTime,  TimeUnit

  • 非常适合新手学生的Java线程池超详细分析

    目录 线程池的好处 创建线程池的五种方式 缓存线程池CachedThreadPool 固定容量线程池FixedThreadPool 单个线程池SingleThreadExecutor 定时任务线程池ScheduledThreadPool ThreadPoolExecutor创建线程池(十分推荐) ThreadPoolExecutor的七个参数详解 workQueue handler 如何触发拒绝策略和线程池扩容? 线程池的好处 可以实现线程的复用,避免重新创建线程和销毁线程.创建线程和销毁线程对

  • 一文带你深入剖析Java线程池的前世今生

    目录 由线程到线程池 线程在做什么 为什么需要线程池 线程池实现原理 总结 由线程到线程池 线程在做什么 灵魂拷问:写了那么多代码,你能够用一句话简练描述线程在干啥吗? public class Demo01 {   public static void main(String[] args) {     var thread = new Thread(() -> {       System.out.println("Hello world from a Java thread"

  • Java实现手写线程池的示例代码

    目录 前言 线程池给我们提供的功能 工具介绍 Worker设计 线程池设计 总结 前言 在我们的日常的编程当中,并发是始终离不开的主题,而在并发多线程当中,线程池又是一个不可规避的问题.多线程可以提高我们并发程序的效率,可以让我们不去频繁的申请和释放线程,这是一个很大的花销,而在线程池当中就不需要去频繁的申请线程,他的主要原理是申请完线程之后并不中断,而是不断的去队列当中领取任务,然后执行,反复这样的操作.在本篇文章当中我们主要是介绍线程池的原理,因此我们会自己写一个非常非常简单的线程池,主要帮

  • Java实现手写乞丐版线程池的示例代码

    目录 前言 线程池的具体实现 线程池实现思路 线程池实现代码 线程池测试代码 杂谈 总结 前言 在上篇文章线程池的前世今生当中我们介绍了实现线程池的原理,在这篇文章当中我们主要介绍实现一个非常简易版的线程池,深入的去理解其中的原理,麻雀虽小,五脏俱全. 线程池的具体实现 线程池实现思路 任务保存到哪里? 在上篇文章线程池的前世今生当中我们具体去介绍了线程池当中的原理.在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用execute方法将任务添加到线程池当中)里面去拿任务

  • Java实现手写一个线程池的示例代码

    目录 概述 线程池框架设计 代码实现 阻塞队列的实现 线程池消费端实现 获取任务超时设计 拒绝策略设计 概述 线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和细节吗?如果直接去看jdk源码的话,可能有一定的难度,那么我们可以先通过手写一个简单的线程池框架,去掌握线程池的基本原理后,再去看jdk的线程池源码就会相对容易,而且不容易忘记. 线程池框架设计 我们都知道,线程资源的创建和销毁并不是没有代价的,甚至开销是非常高的.同

  • 利用Java手写一个简易的lombok的示例代码

    目录 1.概述 2.lombok使用方法 3.lombok原理解析 4.手写简易lombok 1.概述 在面向对象编程中,必不可少的需要在代码中定义对象模型,而在基于Java的业务平台开发实践中尤其如此.相信大家在平时开发中也深有感触,本来是没有多少代码开发量的,但是因为定义的业务模型对象比较多,而需要重复写Getter/Setter.构造器方法.字符串输出的ToString方法.Equals/HashCode方法等.我们都知道Lombok能够替大家完成这些繁琐的操作,但是其背后的原理很少有人会

  • Python快速实现一个线程池的示例代码

    目录 楔子 Future 对象 提交函数自动创建 Future 对象 future.set_result 到底干了什么事情 提交多个函数 使用 map 来提交多个函数 按照顺序等待执行 取消一个函数的执行 函数执行时出现异常 等待所有函数执行完毕 小结 楔子 当有多个 IO 密集型的任务要被处理时,我们自然而然会想到多线程.但如果任务非常多,我们不可能每一个任务都启动一个线程去处理,这个时候最好的办法就是实现一个线程池,至于池子里面的线程数量可以根据业务场景进行设置. 比如我们实现一个有 10

  • C++实现一个简单的线程池的示例代码

    目录 一.设计 二.参数选择 三.类设计 一.设计 线程池应该包括 保存线程的容器,保存任务的容器. 为了能保证避免线程对任务的竞态获取,需要对任务队列进行加锁. 为了使得工作线程感知任务的到来,需要使用条件变量来唤醒工作线程. 任务容器中的任务管理. 任务的处理API. 二.参数选择 使用数组存放线程,链表存放任务. 三.类设计 线程池类 template<typename T> class threadpool { public: threadpool(int thread_num,int

  • C++单例模式实现线程池的示例代码

    C语言单例模式实现线程池. 该代码中,使用了单例模式来创建线程池对象,保证了整个程序中只有一个线程池对象. 线程池中包含了任务队列.工作线程数组.互斥锁.条件变量等成员,通过这些成员来实现任务的提交和执行. 在主函数中,提交了10个任务,每个任务都是一个简单的打印数字的函数,最后等待所有任务执行完毕后销毁线程池. #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_POOL

  • java实现手写一个简单版的线程池

    有些人可能对线程池比较陌生,并且更不熟悉线程池的工作原理.所以他们在使用线程的时候,多数情况下都是new Thread来实现多线程.但是,往往良好的多线程设计大多都是使用线程池来实现的. 为什么要使用线程 降低资源的消耗.降低线程创建和销毁的资源消耗.提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间提高线程的可管理性 下图所示为线程池的实现原理:调用方不断向线程池中提交任务:线程池中有一组线程,不断地从队列中取任务,这是一个典型的生产者-消费者模型. 要实现一

  • Java实现手写线程池实例并测试详解

    前言 在之前的文章中介绍过线程池的核心原理,在一次面试中面试官让手写线程池,这块知识忘记的差不多了,因此本篇文章做一个回顾. 希望能够加深自己的印象以及帮助到其他的小伙伴儿们 在线程池核心原理篇介绍过线程池的核心原理,今天来模拟线程池和工作队列的流程,以及编写代码和测试类进行测试.下面附下之前线程池的核心流程: 在线程池核心原理的源码中,涉及到了一系列的流程,包括线程池队列数量是否已满,运用什么样的拒绝策略等.在我们手写线程池的代码中,不需要考虑那么多因素,只需要模拟简单的情景和过程,因此整体来

  • java简单手写版本实现时间轮算法

    时间轮 关于时间轮的介绍,网上有很多,这里就不重复了 核心思想 一个环形数组存储时间轮的所有槽(看你的手表),每个槽对应当前时间轮的最小精度 超过当前时间轮最大表示范围的会被丢到上层时间轮,上层时间轮的最小精度即为下层时间轮能表达的最大时间(时分秒概念) 每个槽对应一个环形链表存储该时间应该被执行的任务 需要一个线程去驱动指针运转,获取到期任务 以下给出java 简单手写版本实现 代码实现 时间轮主数据结构 /** * @author apdoer * @version 1.0 * @date

随机推荐