手写Java线程池与定时器:彻底掌握多线程任务调度

目录

一、线程池

1.1、什么是线程池

1.2、Java标准库中的线程池

1.3、ThreadPoolExecutor的七大参数

1.4、模拟实现线程池

1.4.1、submit ()

1.4.2、构造方法

1.4.3、运行结果

二、定时器​

2.1、标准库中的定时器

2.2、模拟实现定时器

2.2.1、MyTimerTask类

2.2.2、MyTimer类

2.2.3、main方法


一、线程池

1.1、什么是线程池

线程池是一种线程管理技术,用于管理和复用线程。它会预先创建一定数量的线程把他们放入一个池中,当需要执行任务时,它会从线程池中获取空闲的线程来执行任务,执行完任务后,线程又会返回到线程池中等待下一个任务,这样可以避免频繁的创建和销毁线程,有效提高了系统的性能。

1.2、Java标准库中的线程池

Executors.newFixedThreadPool(2) 创建出含有两个线程的线程池

pool.submit()向线程池中提交一个任务

Executors.newFixedThreadPool(2) 方法创建的是使用前台线程的线程池。

java 复制代码
public static void main(String[] args) {
        ExecutorService pool= Executors.newFixedThreadPool(2);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("提交任务...");
            }
        });
    }

Executors 创建线程池的几个方式:

Executors 类 是对 ThreadPoolExecutor类进行了一层封装和简化,隐藏了一些复杂性,使得开发者更容易使用线程池功能。

1.3、ThreadPoolExecutor的七大参数

核心线程数(corePoolSize):线程池中一直存活的线程数量,包括处于空闲状态的

最大线程数(maximumPoolSize) :线程池中允许存在的最大线程数量,核心线程+临时线程

线程空闲时间(keepAliveTime) :临时线程允许空闲的最大时间

线程空闲时间单位(unit):空闲时间单位

任务队列(workQueue) :用于保存等待执行的任务的队列

线程工厂(threadFactory) :用于创建新线程的工厂

拒绝策略(RejectedExecutionHandler) :当线程池已经饱和且无法接受新任务时,用于处理新任务的策略

AbortPolicy( 默认策略):线程池已满且无法接受新任务时,抛出异常,拒绝任务

CallerRunsPolicy :线程池已满时,使调用线程(提交任务的线程)直接执行被拒绝的任务

DiscardPolicy :线程池已满时,直接丢弃新任务,不做任何处理

DiscardOldestPolicy :线程池已满时,丢弃队列中等待时间最长的任务,然后尝试将新任务放入队列中

1.4、模拟实现线程池

java 复制代码
public class MyThreadPool {
    private BlockingDeque<Runnable> deque=null;  //任务队列
    public MyThreadPool(int n) {
        this.deque = new LinkedBlockingDeque<>(n);
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
                try {
                    while (true) {
                        Runnable task = deque.take();
                        task.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }

    }
    public void submit (Runnable task1) throws InterruptedException {
        deque.put(task1);
    }
}
class Main{
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool myThreadPool=new MyThreadPool(10);
        for (int i = 0; i < 100; i++) {
            int id=i;
            myThreadPool.submit(()->{
                System.out.println(Thread.currentThread().getName()+" 在执行任务:"+id);
            });
        }
    }
}
1.4.1、submit ()

注意:队列中任务类型是Runnable类型

1.4.2、构造方法
1.4.3、运行结果

二、定时器

2.1、标准库中的定时器

在Java中,定时器(Timer)是一种用于调度和执行任务的机制。下面代码所以TimerTimerTask 实现一个定时任务,schedule 里面传入两个参数(1)需要执行的任务代码 (2)指定多长时间之后执行

java 复制代码
public static void main(String[] args) {
        Timer timer=new Timer();
        TimerTask timerTask=new TimerTask() {
            @Override
            public void run() {
                System.out.println("3秒后执行此任务...");
            }
        };
        timer.schedule(timerTask,3000);
    }

2.2、模拟实现定时器

要模拟实现上述定时器,我们需要实现以下几点:

1️⃣:创建一个可以表示任务的类

2️⃣:能够管理多个任务的集合类

3️⃣:实现 schedule方法把任务添加到队列中

4️⃣:创建额外的线程执行任务

2.2.1、MyTimerTask类
java 复制代码
class MyTimerTask implements Comparable<MyTimerTask>{
    private long time;
    private Runnable task;
    public MyTimerTask(Runnable task,long time){
        this.task=task;
        this.time=time;
    }
    @Override
    public int compareTo(MyTimerTask o) {//重写,进行时间上的比较
        return Long.compare(this.time,o.time);
    }

    public long getTime() {
        return time;
    }
    public void run(){
        task.run();
    }
}

MyTimerTask 为表示任务的类,成员变量包含指定的时间和需要执行的任务,在构造方法中传入时间和Runnable 类型的任务,重写compare 方法为了仅比较MyTimerTask 对象中的time变量的大小

2.2.2、MyTimer类
java 复制代码
class MyTimer{
    public PriorityQueue<MyTimerTask> queue=new PriorityQueue<>();//集合类
    Object locker=new Object();//锁对象
    public MyTimer(){
        Thread t=new Thread(()->{
            try {
                while (true) {
                    synchronized (locker){
                        while (queue.isEmpty()) {//队列为空时
                            locker.wait();
                        }
                        MyTimerTask myTimerTask = queue.peek();
                        if (myTimerTask.getTime() > System.currentTimeMillis()) {//队列不为空,但时间未到
                            locker.wait(myTimerTask.getTime()-System.currentTimeMillis());//等待时间差
                        } else {
                            myTimerTask.run();
                            queue.poll();
                        }
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
        t.start();
    }
    public void schedule(Runnable task,long delay){ //将任务添加到队列中
        synchronized (locker){
           MyTimerTask timetask=new MyTimerTask(task,delay+System.currentTimeMillis());
           queue.offer(timetask);
           locker.notifyAll();
        }
    }
}

下述任务队列我们采用优先级队列,为什么不使用ArrayList 或其他数据结构呢?主要原因还是要向任务队列传入两个参数,其中一个为时间,我们需要按任务时间的长短进行排序, 倘若使用ArrayList我们每次执行任务时,就需要遍历整个线性表找到时间最短的任务,实在太麻烦~~,不如在插入任务时就排序成有序队列,所以采用优先级队列。

最近几次使用synchronized 时搭配while循环是一种防御性编程策略,二次校验,阻塞等待唤醒,但下面的 if 语句不可替换,我们需要按实际需求来使用比较适合的方案。

schedule 方法如下

2.2.3、main方法
java 复制代码
public class Demo19 {
    public static void main(String[] args) {
        MyTimer myTimer=new MyTimer();
        myTimer.schedule(new MyRunable(){
            @Override
            public void run() {
                System.out.println("3000ms秒后执行");
            }
        },3000);
        myTimer.schedule(new MyRunable(){
            @Override
            public void run() {
                System.out.println("2000ms秒后执行");
            }
        },2000);
        myTimer.schedule(new MyRunable(){
            @Override
            public void run() {
                System.out.println("1000ms秒后执行");
            }
        },1000);
    }
}

我们向任务队列中提交三个任务,分别在1、2、3秒后执行,执行程序,运行结果如图:

上述代码就是我们实现的简单的定时器,但是比起Java标准库中自带的定时器,缺陷还是很多的,但是底层逻辑大似相同,另外该定时器是一个额外的线程来按时间大小执行队列中的任务,但是如果同一时间,有大量任务添加到队列中,我们此处的单一线程中同一时间恐怕执行不了如此多的任务。比如中12:00,突然有1000000~个滑稽🤪提交了需要立马执行的任务:

这时我们可以使用线程池,让一个线程负责扫描,将需要执行的任务添加到线程池的任务队列中,让多个线程负责执行。

相关推荐
洛小豆几秒前
饭票、图书馆、GC:这样理解 Java 引用,谁还不会?
java·后端·面试
SimonLiu00916 分钟前
清理HiNas(海纳斯) Docker日志并限制日志大小
java·docker·容器
带刺的坐椅17 分钟前
开发 MCP Proxy(代理)也可以用 Solon AI MCP 哟!
java·ai·llm·solon·mcp·mcp-server·mcp-client
yuren_xia32 分钟前
Spring XML 配置
xml·java·spring
每次的天空38 分钟前
kotlin与MVVM结合使用总结(三)
开发语言·microsoft·kotlin
keep intensify1 小时前
通讯录完善版本(详细讲解+源码)
c语言·开发语言·数据结构·算法
小鸡脚来咯1 小时前
SpringBoot 常用注解大全
java
ephemerals__1 小时前
【c++11】c++11新特性(下)(可变参数模板、default和delete、容器新设定、包装器)
开发语言·c++
先生沉默先1 小时前
c#接口_抽象类_多态学习
开发语言·学习·c#
风铃儿~1 小时前
Java面试高频问题(26-28)
java·算法·面试