实现java简单的线程池

目录
  • 拆分实现流程
  • 实现方式
    • 1.拒绝策略
    • 2.阻塞队列
    • 3.线程池和工作线程
    • 策略模式
  • 对比JDK的线程池
    • 线程池的状态转化
  • 总结

拆分实现流程

请看下面这张图

首先我们得对线程池进行一个功能拆分

  • Thread Pool 就是我们的线程池,t1,t2,t3代表三个线程
  • Blocking Queue代表阻塞队列
  • main代表main方法的线程
  • task1,task2,task3代表要执行的每个任务

现在我们梳理一下执行的流程,注意这里是简略版的,文章后面我会给出详细版的

所以此时,我们发现了需要创建几个类,或者说几个角色,分别是

  • 线程池
  • 工作线程
  • 阻塞队列
  • 拒绝策略(干嘛的?就是当线程数已经满了,并且阻塞队列也满了,还有任务想进入阻塞队列的时候,就可以拒绝这个任务)

实现方式

1.拒绝策略

/**
 * 拒绝策略
 */
@FunctionalInterface
interface RejectPolicy<T>{
	//queue就是我们自己实现的阻塞队列,task是任务
    void reject(BlockingQueue<T> queue,T task);
}

2.阻塞队列

我们需要实现四个方法,获取和添加,超时获取和超时添加,至于方法实现的细节,我都备注了大量的注释进行解释。

/**
 * 阻塞队列
 */
class BlockingQueue<T>{
    //阻塞队列
    private Deque<T> queue = new ArrayDeque<>();

    //锁
    private ReentrantLock lock = new ReentrantLock();

    //生产者条件变量
    private Condition fullWaitSet = lock.newCondition();

    //消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();

    //容量
    private int capacity;

    public BlockingQueue(int capacity){
        this.capacity = capacity;
    }

    //带有超时阻塞获取
    public T poll(long timeout, TimeUnit timeUnit){
        lock.lock();
        try {
            //将timeout统一转换为纳秒
            long nanos = timeUnit.toNanos(timeout);
            while(queue.isEmpty()){
                try {
                    if(nanos <= 0){
                        //小于0,说明上次没有获取到,代表已经超时了
                        return null;
                    }
                    //返回值是剩余的时间
                    nanos = emptyWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            //通知生产者
            fullWaitSet.signal();
            return t;
        }finally {
            lock.unlock();
        }
    }

    //阻塞获取
    public T take(){
        lock.lock();
        try{
            while(queue.isEmpty()){ //如果任务队列为空,代表线程池没有可以执行的内容
                try {
                     /*
                    也就说此时进来的线程是执行不了任务的,所以此时emptyWaitSet消费者要进行阻塞状态
                    等待下一次唤醒,然后继续判断队列是否为空
                     */
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            /*
            代码执行到这里。说明任务队列不为空,线程池就从任务队列拿出一个任务出来执行
            也就是说把阻塞队列的一个任务出队
             */
            T t = queue.removeFirst();
            /*
            然后唤醒之前存放在生成者Condition休息室,因为由于之前阻塞队列已满,fullWaitSet才会进入阻塞状态
            所以当阻塞队列删除了任务,就要唤醒之前进入阻塞状态的fullWaitSet
             */
            fullWaitSet.signal();
            //返回任务
            return t;
        }finally {
            lock.unlock();
        }
    }

    //阻塞添加
    public void put(T task){
        lock.lock();
        try {
            while(queue.size() == capacity){    //任务队列满了
                try {
                    System.out.println("等待加入任务队列"+task);
                    /*
                    也就说此时进来的任务是进不了阻塞队列的,已经满了,所以此时生产者Condition要进入阻塞状态
                    等待下一次唤醒,然后继续判断队列是否为空
                     */
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //任务队列还未满
            System.out.println("加入任务队列"+task);
            //把任务加入阻塞队列
            queue.addLast(task);
            /*
            然后唤醒之前存放在消费者Condition休息室,因为由于之前阻塞队列为空,emptyWaitSet才会进入阻塞状态
            所以当阻塞队列加入了任务,就要唤醒之前进入阻塞状态的emptyWaitSet
             */
            emptyWaitSet.signal();
        }finally {
            lock.unlock();
        }
    }

    //带超时阻塞时间添加
    public boolean offer(T task,long timeout,TimeUnit timeUnit){
        lock.lock();
        try {
            long nanos = timeUnit.toNanos(timeout);
            while(queue.size() == capacity){
                try {
                    if(nanos < 0){
                        return false;
                    }
                    System.out.println("等待加入任务队列"+task);
                    //不会一直阻塞,超时就会继续向下执行
                    nanos = fullWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("加入任务队列"+task);
            queue.addLast(task);
            emptyWaitSet.signal();
            return true;
        }finally {
            lock.unlock();
        }
    }

    //获取任务数量
    public int size(){
        lock.lock();
        try{
            return queue.size();
        }finally {
            lock.unlock();
        }
    }

    //尝试添加任务,如果阻塞队列已经满了,就使用拒绝策略
    public void tryPut(RejectPolicy<T> rejectPolicy, T task){
        lock.lock();
        try {
            //判断队列是否已满
            if(queue.size() == capacity){
                rejectPolicy.reject(this,task);
            }else{  //有空闲
                System.out.println("加入任务队列"+task);
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        }finally {
            lock.unlock();
        }
    }
}

3.线程池和工作线程

我把工作线程当成线程池的内部类去实现。方便调用变量。

/**
 * 线程池
 */
class ThreadPool{
    //阻塞队列
    private BlockingQueue<Runnable> taskQueue;

    //线程集合
    private HashSet<Worker> workers = new HashSet<>();

    //核心线程数
    private int coreSize;

    //获取任务的超时时间
    private long timeout;

    private TimeUnit timeUnit;

    private RejectPolicy<Runnable> rejectPolicy;

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity,RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapacity);
        this.rejectPolicy = rejectPolicy;
    }

    //执行任务
    public void execute(Runnable task){
        synchronized (workers){
            if(workers.size() <= coreSize){  //当前的线程数小于核心线程数
                Worker worker = new Worker(task);
                workers.add(worker);
                //让线程开始工作,执行它的run方法
                worker.start();
            }else{
                // 1) 死等
                // 2) 带超时等待
                // 3) 让调用者放弃任务执行
                // 4) 让调用者抛出异常
                // 5) 让调用者自己执行任务
                taskQueue.tryPut(rejectPolicy,task);
            }
        }
    }

    /**
     * 工作线程,也就是线程池里面的线程
     */
    class Worker extends Thread{
        private Runnable task;
        public Worker(Runnable task){
            this.task = task;
        }

        @Override
        public void run() {
            //执行任务
            // 1) 当 task 不为空,执行任务
            // 2) 当 task 执行完毕,再接着从任务队列获取任务并执行
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                try {
                    System.out.println("正在执行的任务" + task);
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //代表这个任务已经执行完了
                    task = null;
                }
            }
            synchronized (workers) {
                System.out.println("worker 被移除" + this);
                workers.remove(this);
            }
        }
    }
}

策略模式

细心的小伙伴已经发现,我在拒绝策略这里使用了23种设计模式的策略模式,因为我没有将拒绝的方式写死,而是交给了调用者去实现。

对比JDK的线程池

下面是JDK自带的线程池

经典的七大核心参数

  • corePoolSize:核心线程数
  • queueCapacity:任务队列容量(阻塞队列)
  • maxPoolSize:最大线程数
  • keepAliveTime:线程空闲时间
  • TimeUnit unit:超时时间单位
  • ThreadFactory threadFactory:线程工程
  • rejectedExecutionHandler:任务拒绝处理器

实际上我们自己实现的也大同小异,只不过JDK官方的更为复杂。

JDK线程执行的流程图

线程池的状态转化

线程我们知道在操作系统层面有5种状态

  • 初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 可运行状态(就绪状态):指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 运行状态:指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 阻塞状态
  • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
  • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
  • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  • 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

线程在Java API层面有6种状态

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的
  • RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
  • TERMINATED 当线程代码运行结束

线程池有5种状态

  • RUNNING:能接受新任务,并处理阻塞队列中的任务
  • SHUTDOWN:不接受新任务,但是可以处理阻塞队列中的任务
  • STOP:不接受新任务,并且不处理阻塞队列中的任务,并且还打断正在运行任务的线程,就是直接不干了!
  • TIDYING:所有任务都终止,并且工作线程也为0,处于关闭之前的状态
  • TERMINATED:已关闭。

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 如何理解Java线程池及其使用方法

    目录 一.前言 二.总体的架构 三.研读ThreadPoolExecutor 3.1.任务缓存队列 3.2.拒绝策略 3.3.线程池的任务处理策略 3.4.线程池的关闭 3.5.源码分析 四.常见的四种线程池 4.1.newFixedThreadPool 4.2.newSingleThreadExecutor 4.3.newCachedThreadPool 4.4.newScheduledThreadPool 五.使用实例 5.1.newFixedThreadPool实例 5.2.newCach

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

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

  • 每日六道java新手入门面试题,通往自由的道路--线程池

    目录 1. 你知道线程池吗?为什么需要使用线程池? 小结: 2. 你知道有多少种创建线程池的方式 3. 线程池的五种状态你有了解吗? 4. 你知道ThreadPoolExecutor的构造方法和参数吗 5. 你可以说下线程池的执行过程原理吗 6. 能否写一个简单线程池的demo? 总结 1. 你知道线程池吗?为什么需要使用线程池? 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源. 而在Java中, JVM 中每创建和销毁线程就需要资源和时间的损耗了,

  • Java如何固定大小的线程池

    1.固定大小的线程池简介 线程池就是在程序启动的时候先建立几个可以使用的线程放在那里,然后等着具体的任务放进去,这个任务基本可以说都是Runnable的实现类,因此它减小了系统每次新建和销毁线程的开销,但同时增加了维护这些线程的开销,个中取舍看具体情况而定. 固定大小的线程池就是在启动的时候创建了固定个数的线程放在那里等待使用. 2.包装一个线程池对象 public class TaskPool{ private final ThreadPoolExecutor executor = (Thre

  • Java线程池中的各个参数如何合理设置

    一.前言 在开发过程中,好多场景要用到线程池.每次都是自己根据业务场景来设置线程池中的各个参数. 这两天又有需求碰到了,索性总结一下方便以后再遇到可以直接看着用. 虽说根据业务场景来设置各个参数的值,但有些万变不离其宗,掌握它的原理对如何用好线程池起了至关重要的作用. 那我们接下来就来进行线程池的分析. 二.ThreadPoolExecutor的重要参数 我们先来看下ThreadPoolExecutor的带的那些重要参数的构造器. public ThreadPoolExecutor(int co

  • Java简单实现线程池

    本文实例为大家分享了Java简单实现线程池的具体代码,供大家参考,具体内容如下 一.线程池 线程池是一种缓冲提高效率的技术. 相当于一个池子,里面存放大量已经创建好的线程,当有一个任务需要处理时, 可以直接从池子里面取一个线程去执行它. 包括内存池,很多缓冲的技术都是采用这种技术. 其实理解起来很简答! 为什么需要线程池,这种池的技术? 1.1 减少开辟资源和销毁资源带来的损耗. 开辟线程,申请内存(具体的可以看C语言中malloc底层实现原理),销毁线程.释放内存资源等一些操作都是有时间消耗的

  • 实现java简单的线程池

    目录 拆分实现流程 实现方式 1.拒绝策略 2.阻塞队列 3.线程池和工作线程 策略模式 对比JDK的线程池 线程池的状态转化 总结 拆分实现流程 请看下面这张图 首先我们得对线程池进行一个功能拆分 Thread Pool 就是我们的线程池,t1,t2,t3代表三个线程 Blocking Queue代表阻塞队列 main代表main方法的线程 task1,task2,task3代表要执行的每个任务 现在我们梳理一下执行的流程,注意这里是简略版的,文章后面我会给出详细版的 所以此时,我们发现了需要

  • Java手写线程池的实现方法

    本文实例为大家分享了Java手写线程池的实现代码,供大家参考,具体内容如下 1.线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程. 2.线程池简易架构 3.简易线程池代码(自行优化) import java.util.List; /** * 线程接口 * * @Author yjian * @Date 14:49 2017/10/14 **/ public interface IThreadPool { //加入任务 void ex

  • Java实现终止线程池中正在运行的定时任务

    最近项目中遇到了一个新的需求,就是实现一个可以动态添加定时任务的功能.说到这里,有人可能会说简单啊,使用quartz就好了,简单粗暴.然而quartz框架太重了,小项目根本不好操作啊.当然,也有人会说,jdk提供了timer的接口啊,完全够用啊.但是我们项目的需求完全是多线程的模型啊,而timer是单线程的,so,楼主最后还是选择了jdk的线程池. 线程池是什么 Java通过Executors提供四种线程池,分别为: newCachedThreadPool :创建一个可缓存线程池,如果线程池长度

  • 一文彻底搞懂java多线程和线程池

    目录 什么是线程 一. Java实现线程的三种方式 1.1.继承Thread类 1.2.实现Runnable接口,并覆写run方法 二. Callable接口 2.1 Callable接口 2.2 Future接口 2.3 Future实现类是FutureTask. 三. Java线程池 3.1.背景 3.2.作用 3.3.应用范围 四. Java 线程池框架Executor 4.1.类图: 4.2 核心类ThreadPoolExecutor: 4.3 ThreadPoolExecutor逻辑结

  • Java多线程与线程池技术分享

    目录 一.序言 1.普通执行 2.线程池执行 二.线程池基础 1.核心参数 2.参数与池的关系 1.通用对比 2.拓展对比 3.无返回值任务 4.有返回值任务 三.Executors 1.创建单一线程的线程池 2.创建固定数量的线程池 3.创建可伸缩的线程池 4.创建定时调度的线程池 四.手动创建线程池 五.其它 1.配置线程池的参数 2.线程池监控 六.总结 一.序言 Java多线程编程线程池被广泛使用,甚至成为了标配. 线程池本质是池化技术的应用,和连接池类似,创建连接与关闭连接属于耗时操作

  • Java手写线程池之向JDK线程池进发

    目录 前言 JDK线程池一瞥 自己动手实现线程池 线程池参数介绍 实现Runnable 实现Callable 拒绝策略的实现 线程池关闭实现 工作线程的工作实现 线程池实现的BUG 完整代码 线程池测试 总结 前言 在前面的文章自己动手写乞丐版线程池中,我们写了一个非常简单的线程池实现,这个只是一个非常简单的实现,在本篇文章当中我们将要实现一个和JDK内部实现的线程池非常相似的线程池. JDK线程池一瞥 我们首先看一个JDK给我们提供的线程池ThreadPoolExecutor的构造函数的参数:

  • 详谈Java几种线程池类型介绍及使用方法

    一.线程池使用场景 •单个任务处理时间短 •将需处理的任务数量大 二.使用Java线程池好处 1.使用new Thread()创建线程的弊端: •每次通过new Thread()创建对象性能不佳. •线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom. •缺乏更多功能,如定时执行.定期执行.线程中断. 2.使用Java线程池的好处: •重用存在的线程,减少对象创建.消亡的开销,提升性能. •可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞

  • 详解Java中的线程池

    1.简介 使用线程池可以避免线程的频繁创建以及销毁. JAVA中提供的用于实现线程池的API: Executor.ExecutorService.AbstractExecutorService.ThreadPoolExecutor.ForkJoinPool都位于java.util.concurrent包下. *ThreadPoolExecutor.ForkJoinPool为线程池的实现类. 2.Executor public interface Executor { /** * 向线程池提交一个

  • C++11 简单实现线程池的方法

    什么是线程池 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙.如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线程可以排队,但他们要等到其他线程完成后才启动. 不使用

随机推荐