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

 更新时间:2021年08月02日 15:43:33   作者:迷路国王  
有些人可能对线程池比较陌生,并且更不熟悉线程池的工作原理。本文就来手写一个简单版的线程池,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

有些人可能对线程池比较陌生,并且更不熟悉线程池的工作原理。所以他们在使用线程的时候,多数情况下都是new Thread来实现多线程。但是,往往良好的多线程设计大多都是使用线程池来实现的。 为什么要使用线程 降低资源的消耗。降低线程创建和销毁的资源消耗。提高响应速度:线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间提高线程的可管理性

下图所示为线程池的实现原理:调用方不断向线程池中提交任务;线程池中有一组线程,不断地从队列中取任务,这是一个典型的生产者-消费者模型。

要实现一个线程池,有几个问题需要考虑:

  • 队列设置多长?如果是无界的,调用方不断往队列中方任务,可能导致内存耗尽。如果是有界的,当队列满了之后,调用方如何处理?
  • 线程池中的线程个数是固定的,还是动态变化的?
  • 每次提交新任务,是放入队列?还是开新线程
  • 当没有任务的时候,线程是睡眠一小段时间?还是进入阻塞?如果进入阻塞,如何唤醒?

针对问题4,有3种做法:

  • 不使用阻塞队列,只使用一般的线程安全的队列,也无阻塞/唤醒机制。当队列为空时,线程池中的线程只能睡眠一会儿,然后醒来去看队列中有没有新任务到来,如此不断轮询。
  • 不使用阻塞队列,但在队列外部,线程池内部实现了阻塞/唤醒机制
  • 使用阻塞队列

很显然,做法3最完善,既避免了线程池内部自己实现阻塞/唤醒机制的麻烦,也避免了做法1的睡眠/轮询带来的资源消耗和延迟。
现在来带大家手写一个简单的线程池,让大家更加理解线程池的工作原理

实战:手写简易线程池

根据上图可以知道,实现线程池需要一个阻塞队列+存放线程的容器

/**
 * Five在努力
 * 自定义线程池
 */
public class ThreadPool {

    /** 默认线程池中的线程的数量 */
    private static final int WORK_NUM = 5;

    /** 默认处理任务的数量 */
    private static final int TASK_NUM = 100;

    /** 存放任务 */
    private final BlockingQueue<Runnable> taskQueue;

    private final Set<WorkThread> workThreads;//保存线程的集合

    private int workNumber;//线程数量

    private int taskNumber;//任务数量

    public ThreadPool(){
        this(WORK_NUM , TASK_NUM);
    }

    public ThreadPool(int workNumber , int taskNumber) {
        if (taskNumber<=0){
            taskNumber = TASK_NUM;
        }
        if (workNumber<=0){
            workNumber = WORK_NUM;
        }
        this.taskQueue = new ArrayBlockingQueue<Runnable>(taskNumber);
        this.workNumber = workNumber;
        this.taskNumber = taskNumber;

        workThreads = new HashSet<>();

        //工作线程准备好了
        //启动一定数量的线程数,从队列中获取任务处理
        for (int i=0;i<workNumber;i++) {
            WorkThread workThread = new WorkThread("thead_"+i);
            workThread.start();
            workThreads.add(workThread);
        }
    }

    /**
     * 线程池执行任务的方法,其实就是往BlockingQueue中添加元素
     * @param task
     */
    public void execute(Runnable task) {
        try {
            taskQueue.put(task);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }


    /**
     * 销毁线程池
     */
    public void destroy(){
        System.out.println("ready close pool...");
        for (WorkThread workThread : workThreads) {
            workThread.stopWorker();
            workThread = null;//help gc
        }
        workThreads.clear();
    }

    /** 内部类,工作线程的实现 */
    private class WorkThread extends Thread{
        public WorkThread(String name){
            super();
            setName(name);
        }
        @Override
        public void run() {
            while (!interrupted()) {
                try {
                    Runnable runnable = taskQueue.take();//获取任务
                    if (runnable !=null) {
                        System.out.println(getName()+" ready execute:"+runnable.toString());
                        runnable.run();//执行任务
                    }
                    runnable = null;//help gc
                } catch (Exception e) {
                    interrupt();
                    e.printStackTrace();
                }
            }
        }

        public void stopWorker(){
            interrupt();
        }
    }
}

上面代码定义了默认的线程数量和默认处理任务数量,同时用户也可以自定义线程数量和处理任务数量。用BlockingQueue阻塞队列来存放任务。用set来存放工作线程,set的好处就不用多说了。懂的都懂

构造方法中new对象的时候,循环启动线程,并把线程放入set中。WorkThread实现Thread,run方法实现也很简单,因为有一个stop方法,所以这里需要while判断,之后从taskQueue队列中,获取任务。如何获取不到就阻塞,获取到的话runnable.run();就执行任务,之后把任务变成null

销毁线程只需要遍历set,把每个线程停止,并且变为null就行了

执行线程任务execute,只需要从往阻塞队列中添加任务就行了

测试一下:

public class TestMySelfThreadPool {

    private static final int TASK_NUM = 50;//任务的个数

    public static void main(String[] args) {
        ThreadPool myPool = new ThreadPool(3,50);
        for (int i=0;i<TASK_NUM;i++) {
            myPool.execute(new MyTask("task_"+i));
        }

    }

    static class MyTask implements Runnable{

        private String name;
        public MyTask(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }


        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("task :"+name+" end...");

        }

        @Override
        public String toString() {
            // TODO Auto-generated method stub
            return "name = "+name;
        }
    }
}

在这里插入图片描述

结果ok。没什么问题

到此这篇关于java实现手写一个简单版的线程池的文章就介绍到这了,更多相关java 手写线程池内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java数组转List及Stream的基本方法使用方法

    Java数组转List及Stream的基本方法使用方法

    Java 的 Stream 流操作是一种简洁而强大的处理集合数据的方式,允许对数据进行高效的操作,如过滤、映射、排序和聚合,这篇文章主要介绍了Java数组转List及Stream的基本方法使用教程,需要的朋友可以参考下
    2024-08-08
  • 解决JSONObject.toJSONString()输出null的问题

    解决JSONObject.toJSONString()输出null的问题

    这篇文章主要介绍了解决JSONObject.toJSONString()输出null的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Spring如何自定义XML配置扩展

    Spring如何自定义XML配置扩展

    这篇文章主要介绍了Spring如何自定义XML配置扩展,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • SpringBoot如何使用自定义注解实现接口限流

    SpringBoot如何使用自定义注解实现接口限流

    这篇文章主要介绍了SpringBoot如何使用自定义注解实现接口限流,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java中的Lock与ReentrantLock深入解析

    Java中的Lock与ReentrantLock深入解析

    这篇文章主要介绍了Java中的Lock与ReentrantLock深入解析,Lock位于java.util.concurrent.locks包下,是一种线程同步机制,就像synchronized块一样,但是,Lock比synchronized块更灵活、更复杂,需要的朋友可以参考下
    2024-01-01
  • java工具类StringUtils使用实例详解

    java工具类StringUtils使用实例详解

    这篇文章主要为大家介绍了java工具类StringUtils使用实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Java中实例初始化和静态初始化的过程详解

    Java中实例初始化和静态初始化的过程详解

    Java代码初始化块是Java语言中的一个非常重要的概念。初始化块负责在创建对象时进行一些必要的操作,例如设置对象的初始状态、初始化成员变量等。初始化块被分为实例初始化块和静态初始化块两种类型。本文详细介绍了初始化的过程,需要的朋友可以参考下
    2023-05-05
  • 一篇文章带你初步认识Maven

    一篇文章带你初步认识Maven

    这篇文章主要为大家初步认识了Maven,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • java基础(System.err和System.out)详解

    java基础(System.err和System.out)详解

    下面小编就为大家带来一篇java基础(System.err和System.out)详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • 详细解读AbstractStringBuilder类源码

    详细解读AbstractStringBuilder类源码

    这篇文章主要介绍了详细解读AbstractStringBuilder类源码,具有一定参考价值,需要的朋友可以了解下。
    2017-12-12

最新评论