Java编程--定时器/线程池/工厂模式/ ThreadPoolExecutor

       前言 

        逆水行舟,不进则退!!!     


       目录

       什么是定时器      

       实现一个定时器 

自己实现一个定时器

       什么是线程池 

        线程池的使用:

什么是工厂模式?

自己实现一个线程池:

       ThreadPoolExecutor 类

什么是Runnable 任务?

什么是 Callable 任务?

获取异步的执行结果 是什么意思?

ThreadPoolExecutor类的构造方法有7个参数,


       什么是定时器      

        在Java编程中,定时器是一种工具,它用于在指定的时间点主动触发某个事件,而无需外力去开启或启动。这种机制可以节省人力并实现统一管理。 其中,java.util.Timer类是最常用的定时器实现方式,它允许开发者安排在指定时间运行的任务。 使用Timer类创建定时器主要包括以下步骤:

        首先,创建一个Timer对象;

        其次,创建一个TimerTask对象,该对象包含了要执行的代码;

        然后,将TimerTask对象添加到Timer对象中;

        最后,调用Timer对象的schedule方法来安排任务的执行。

         此外,定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以Timer对象一般又和多线程技术结合紧密。

        定时器的使用:

import java.util.Timer;
import java.util.TimerTask;

public class ThreadDemo10 {
    public static void main(String[] args) {
        System.out.println("程序启动");
        // Timer 类就是 标准库的定时器
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {   //TimerTask是timer.schedule方法的第一个参数,
                                // 其实就是Runnable,TimerTask就是一个实现了Runnable的抽象类。
                                // 也要通过run() 方法来描述一段代码。
            @Override
            public void run() {
               
                System.out.println("运行定时器任务3");
            }
        }, 3000);  // TimerTask 的第二个参数是,指定的时间,
                        //  意思就是,一段时间后,触发第一个参数描述的代码。

        
        // 多写两个,感受一下定时执行的任务
        timer.schedule(new TimerTask() {   
            @Override
            public void run() {
                System.out.println("运行定时器任务2");
            }
        }, 2000);  



        timer.schedule(new TimerTask() {   

            @Override
            public void run() {
                System.out.println("运行定时器任务1");
            }
        }, 1000); 


    }
}


       实现一个定时器 

自己实现一个定时器

        分析:
               1,让被注册的任务,能够在指定时间被执行。
               2,一个定时器是可以注册 N 个任务的,N 个任务会按照最初约定的时间,按顺序执行。
               若要实现 1, 就需要在定时器内部,单独弄一个线程,让这个线程周期性的扫描,判断任务是否是到时间了。如果到时间了,就执行,没到时间,就再等等。并且 N 个任务也需要保存。
 
 所以,定时器中的核心:
         1, 有一个扫描线程,负责查看时间到没到,到了就执行相应任务
         2, 还要有一个数据结构,来保存所有被注册的任务。
         选用什么数据结构呢:  每个任务都是带着“时间”的, 所以我们这里选用优先级队列来存储。 时间段小的,优先级就高, 此时的扫描线程只用扫描队首元素即可,不必遍历整个队列。

     此处的优先级队列是要在 多线程  环境下使用,要考虑线程安全问题。




import java.util.concurrent.PriorityBlockingQueue;

//开始写 定时器
class MyTimer {
    //扫描线程
    private Thread t = null;

    // 有一个阻塞优先级队列, 来保存任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    //扫描线程的具体实现
    public MyTimer() {
        t = new Thread(() -> {
            while(true) {

                try {
                    //取出队首元素,检查看看队首元素任务是否到时间了,
                    //如果时间没到,就把任务再塞回队列中
                    //如果时间到了,就执行任务。
                    MyTask myTask = queue.take();   // 将任务从 阻塞优先级队列中拿出来。
                    synchronized (this) {          // 这里使用 wait   主要是为了防止 忙等。
                        //  这个 synchronized 本来是放到 wait 那里,
                        //  放到这里是为了 保证 取出任务 和 wait  原子化, 防止在中间线程被调度走而且同时来了新任务。
                        long curTime = System.currentTimeMillis();  //获取当前的时间
                        if (curTime < myTask.getTime()) {
                            //说明当前的时间 还没到要执行任务的时间
                            queue.put(myTask);   // 再把任务 放回到阻塞优先级队列中。


                            //在 put 之后, 进行wait 等待
                            // 等待指定时间
                            this.wait(myTask.getTime() - curTime);  // wait 操作,要搭配 锁 来进行的

                        } else {
                            // 已经到了要执行任务的时间了, 可以开始执行任务了。
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        });
        t.start();
    }

    //提供一个 schedule() 方法,来注册任务 : 两个参数:
    //第一个参数:执行的 任务
    //第二个参数:执行任务前等待的时间。
    public void schedule(Runnable runnable, long after) {
        //第二个参数这里,需要换算为 : 当前的时刻 + 需要等待的时间。
        MyTask task = new MyTask(runnable, System.currentTimeMillis() + after);
        queue.put(task);
        synchronized (this) {
            this.notify(); // 唤醒一下扫描线程
        }
    }


}


//任务的具体描述
class MyTask implements Comparable<MyTask> {
    //要执行的任务内容
    private Runnable runnable;
    // 任务在啥时候执行(使用 毫秒时间戳)
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }

    //获取当前任务的时间
    public long getTime() {
        return time;
    }

    //执行任务
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
        // 返回   小于0, 大于0, 0  这三个数字
        // this 比 o 大, 返回  >0;
        return (int)(this.time - o.time);
    }
}





public class ThreadDemo11 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务1");
            }
        }, 1000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        }, 2000);
    }
}

        注意:

                1,在创建一个新任务那里,在将任务创建好,放入堆中之后,有一步唤醒操作,这里的唤醒操作是唤醒扫描线程那里的等待。在扫描线程中,会将最优先的任务拿出来看看,如果还没到执行的时间,那就再将任务放回到优先级队列中,并且阻塞需要等待的时间。就是这里,如果在阻塞等待时间内,又来了一个优先级更高的任务(时间更短,比刚刚阻塞等待的时间还短),就需要扫描线程重新去优先级队列中拿出最优先的任务,重新计算阻塞等待的时间,然后阻塞等待。所以说,这里的这个唤醒机制很有必要。 

                2,在扫描线程中,synchronized(this) 这行代码 锁住的是实现MyTimer类 的 对象,同一时间,只能有一个线程可以访问到这个对象。

                拓展:如果类中有多个静态方法,使用synchronized 修饰其中一个静态方法,那也同样是对整个类进行了加锁,同一时间,只能有一个线程可以访问到该类的任何静态方法。但是并没有对实现这个类的对象加锁。


       什么是线程池 

        线程池: 为了使多线程开发更高效,使多线程的使用更轻便,而产生。事先把需要使用的线程创建好,放到“池”中,后面需要使用的时候,直接从池里获取,用完后也还给 “池”,  这两个动作要比 创建/销毁 更高效。

        创建线程/销毁线程 是交给 操作系统内核 完成的。而从池子里获取/还给池, 是咱们自己用户代码就能实现的,不必交给内核操作。

        什么是操作系统内核?

               答:操作系统内核是操作系统最基本的部分,是一个提供硬件抽象层、磁盘及文件系统控制、多任务等功能的系统软件。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件操作多长时间。直接对硬件操作是非常复杂的,所以内核通常提供一种硬件抽象的方法来完成这些操作。

       无论是商业的还是个人开发的操作系统内核,都被视为计算机系统的基石和黑盒。这意味着用户通常不需要知道内核内部是如何实现的,只需要使用该内核提供的服务即可。

       我们不清楚内核的具体行为,也就意味着不可控,当我们将任务交给操作系统内核,何时等到系统的回应也是不可控的,因为系统内核不仅仅这一个任务。所以,相比于内核来说,用户态,执行程序的行为是可控的

        线程池的使用:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 使用标准库中的线程池
public class ThreadDemo12 {
    public static void main(String[] args) {
        // 创建了一个固定线程数目的线程池
        // 这里的创建 使用了工厂模式
        ExecutorService pool = Executors.newFixedThreadPool(10);
        
        for(int i = 0; i <1000; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {       // 这个 run() 方法 不是由主线程调用的,
                    // 而是由线程池中的线程调用。
                    System.out.println("hello " + n);
                }
            });
        }
    }
}

什么是工厂模式?

        答:用一句话表示:使用普通方法,来代替构造方法创建对象。那为什么需要代替构造方法了?因为在某些情况下,我们可能要构造多个不同情况的对象,但是使用构造方法的重载又有一些局限性(重载方法 名称相同,参数个数和类型不同),这种情况对我们实现一些代码时有些限制,于是就有了工厂模式。

        普通方法,方法名字没有限制的,因此有多种方法构造,就可以直接使用不同的方法名即可,此时,方法的参数是否要区分就已经不重要了。

自己实现一个线程池:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

class MyThreadPool {

    // 此处不涉及到时间, 此处只有任务,就直接使用 Runnable 即可
    // 阻塞队列中元素的类型为 Runnable 接口类型
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();


    // n 表示线程的数量
    public  MyThreadPool(int n) {
        // 在这里创建线程
        // for循环来创建线程
        for(int i = 0; i < n; i++) {
           
            Thread t = new Thread(() -> {
                
               // while 循环来让线程不断地从队列中取任务。
                while(true) {
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }

    // 注册任务给线程池
    public void submit (Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}


public class ThreadDemo16 {
    public static void main(String[] args) {
        MyThreadPool pool = new MyThreadPool(10);
        for(int i = 0; i < 1000; i++) {
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello " + n);
                }
            });
        }
    }
}


       ThreadPoolExecutor 类

        

 ThreadPoolExecutor : 最原生的线程池

        ThreadPoolExecutor是Java中线程池的核心实现类,它主要用来执行被提交的任务。通过ThreadPoolExecutor的execute()方法,用户可以提交Runnable任务进行执行;而通过submit()方法,用户不仅可以提交Runnable任务和Callable任务,还能获取异步的执行结果。

       使用ThreadPoolExecutor的主要优点在于,当系统中频繁地创建线程时,如果线程过多,会带来调度开销,进而影响缓存局部性和整体性能。而通过使用线程池,可以避免在处理短时间任务时频繁地创建与销毁线程所带来的代价。线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,这既保证了内核的充分利用,又防止了过分调度。

什么是Runnable 任务?

        在Java中,Runnable接口表示一个可以被线程执行的任务,它本身是一个抽象任务,只定义了要执行的操作,并没有具体的实现。这个接口里包含一个无返回值的方法run()。Runnable没有启动线程的能力,因此必须使用Thread类中的start方法才能够启动一个线程。Runnable的run()方法定义没有抛出任何异常,所以任何的Checked Exception都需要在run()实现方法中自行处理。

什么是 Callable 任务?

        与Runnable相似的Callable接口也能被线程执行,但Callable接口的task能返回一个结果,也可以抛出Exception。两者都可以被ExecutorService执行,其中Callable的call()方法只能通过ExecutorService的submit(Callable task)方法来执行,并且会返回一个Future,是表示任务等待完成的对象。

获取异步的执行结果 是什么意思?

        异步执行结果是指在程序执行过程中,某个耗时较长的操作(通常是IO操作或者计算密集型任务)在执行时并不会阻止其它操作的进行,当这个耗时操作完成时,该操作的结果将会被后续的操作或者函数使用。异步编程是一种提高程序性能的方式,它允许同一时间发生(处理)多个事件。

        例如,在Java中,当我们调用一个耗时较长的功能(方法)时,如网络请求或大规模计算,这个方法并不会阻塞程序的执行流程,程序会继续往下执行。当这个功能执行完毕时,比如数据接收完毕或者计算完成,程序能够获得执行完毕的消息或能够访问到执行的结果(如果有返回值或需要返回值时)。

        另外,在更现代的编程模式中,如回调函数、Promises、Futures和CompletableFuture等,可以以非阻塞的方式获取任务执行结果,这种方式不仅提高了程序的响应速度和执行效率,而且使得代码逻辑更加清晰易懂。

ThreadPoolExecutor类的构造方法有7个参数,

分别是:

        1. corePoolSize:线程池中会维护一个最小的线程数量,这些线程处理空闲状态,他们也不会 被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。

        2. maximumPoolSize:线程池允许的最大线程数量。

        3. keepAliveTime:当线程池中的线程数量超过corePoolSize时,多余的空闲线程的存活时间。

        4. unit:keepAliveTime的时间单位。

        5. workQueue:任务队列,用于存放待执行的任务。

        6. threadFactory:创建新线程的工具类。

        7. handler:当线程池中的线程数量超过maximumPoolSize且任务队列已满时,如何处理新提交的任务。

注解:

        1,corePoolSize 核心线程数 和 maximumPoolSize 最大线程数 的区别:

       核心线程数是指线程池中一直保持的线程数,哪怕它们处于空闲状态。这意味着即使线程池中没有任务,这些核心线程也会一直保持存在,以便于快速响应新的任务请求

       最大线程数则是指线程池中允许的最大线程数,这包括了空闲线程和正在工作的线程。如果线程池的任务队列已满且所有的核心线程都在工作,那么此时如果有新任务提交,线程池会根据设置的策略决定是否创建新的线程

       举两个例子:

                1),在一家公司中, 核心线程数就是正式员工, 最大线程数就是 正式员工 + 实习生;当所有的正式员工都在忙碌,并且还有新的任务下来,这时就会招收一些实习生来缓解压力。如果长时间任务比较少,实习生一直在摸鱼(线程空闲),那么就会销毁这个线程,但是呢核心线程数并不会被销毁(即使线程空闲)

                2),假设你开了一家餐厅,这家餐厅的服务员就是线程,而顾客就是任务。核心线程数就好比是你固定的服务员人数,无论餐厅是否忙碌,这些服务员都会在岗位上待命,随时准备为顾客服务。           最大线程数则好比是餐厅能够容纳的最大服务员数量,包括正在工作的和待命的。如果所有的服务员都在工作,并且还有新的顾客进来,那么就需要根据餐厅的规定来决定是否需要再雇佣新的服务员。                   例如,你的餐厅规定,当所有服务员都在工作时,如果有新的顾客进来,那么就不能再接待更多的顾客了,除非有服务员完成他们的工作并腾出位置。这就是最大线程数的作用。

        2,keepAliveTime  简单解释就是 实习生可以摸鱼的最大时间,超过这个线程就销毁。unit: keepAliveTime 是摸鱼时间的时间单位

        3,handler  其实就是一个拒绝策略  ThreadPoolExecutor类的构造方法中,处理提交任务超过线程池最大容量的拒绝策略有四种:

                1) AbortPolicy(默认):直接抛出RejectedExecutionException异常,阻止系统正常运行。

                2) DiscardOldestPolicy:丢弃等待队列中最旧的任务,然后重新尝试执行任务(重复此过程直到能够执行任务为止)。

                3) DiscardPolicy:直接丢弃新来的任务,不抛出异常。

                4) CallerRunsPolicy:让调用者自己运行任务。


        我是专注学习的章鱼哥~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/139597.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【设计模式】策略模式

引例 方案一 说明&#xff1a; 不满足OCP&#xff0c;添加新的排序算法或修改某个已有排序算法需要重新编译整个类可复用性差&#xff0c;Sorting类不可被直接复用 方案二 将客户类和算法类分开 说明&#xff1a;Sorting类可复用&#xff0c;但Sorting类仍不满足OCP 方案三…

用户日期格式不一致导致BDC报时间格式不一致问题

问题描述 在做销售开票的功能时用的BDC&#xff0c;业务在测试的时候总是报日期格式不一致的错误&#xff0c;而我自己测的时候却没啥问题&#xff0c;调试的时候发现是我和业务的时间格式不一致&#xff08;我是YYYYMMDD,他是MMDDYYYY&#xff09;。 解决方案 用函数CONVERT…

解析浏览器的事件循环机制:理解JavaScript运行时的执行顺序

解析浏览器的事件循环机制&#xff1a;理解JavaScript运行时的执行顺序 前言定义执行顺序异步任务概念&#xff1a;微任务、宏任务宏任务有哪些&#xff1f;微任务有哪些 实例代码与图解 前言 因为防止在多个用户同时在浏览器中操作一个DOM节点所带来的复杂性&#xff0c;故Ja…

管理压力:打工人不难为打工人

写在前面 让时间回到2018年7月末&#xff1a; 事件地点&#xff1a;中国平安办公室 事件经过&#xff1a; 平安产品经理提出一个需求&#xff0c;要求APP开发人员根据用户手机壳自动调整颜色的主题。这个需求被程序员认为是不合理的。双方开始争论&#xff0c;情绪激动&…

【Transformer从零开始代码实现 pytoch版】(五)总架构类的实现

Transformer总架构 在实现完输入部分、编码器、解码器和输出部分之后&#xff0c;就可以封装各个部件为一个完整的实体类了。 【Transformer从零开始代码实现 pytoch版】&#xff08;一&#xff09;输入部件&#xff1a;embeddingpositionalEncoding 【Transformer从零开始代…

IP-guard WebServer 命令执行漏洞复现

简介 IP-guard是一款终端安全管理软件&#xff0c;旨在帮助企业保护终端设备安全、数据安全、管理网络使用和简化IT系统管理。在旧版本申请审批的文件预览功能用到了一个开源的插件 flexpaper&#xff0c;使用的这个插件版本存在远程命令执行漏洞&#xff0c;攻击者可利用该漏…

动作捕捉系统通过VRPN与ROS系统通信

NOKOV度量动作捕捉系统支持通过VRPN与机器人操作系统ROS通信&#xff0c;进行动作捕捉数据的传输。 一、加载数据 打开形影动捕软件&#xff0c;加载一段后处理数据。 这里选择一段小车飞机的同步数据。在这段数据里面&#xff0c;场景下包含两个刚体&#xff0c;分别是小车和…

1688商品详情API接口的使用方法、注意事项以及示例代码

1688商品详情API接口使用方法 1688商品详情API接口是1688平台提供的用于获取商品详细信息的接口。通过该接口&#xff0c;您可以获取到商品的ID、名称、价格、销量、评价等信息&#xff0c;从而进行进一步的数据分析和应用开发。本文将介绍1688商品详情API接口的使用方法、注意…

python爬虫top250电影数据

之前看到的&#xff0c;我改了一下&#xff0c;多了很多东西 import requests from bs4 import BeautifulSoup from openpyxl import Workbook from openpyxl.styles import Font import redef extract_movie_info(info):# 使用正则表达式提取信息pattern re.compile(r导演:…

react函数式组件props形式子向父传参

父组件中定义 子组件中触发回调传值 import { useState } from "react"; function Son(params) {const [count, setCount] useState(0);function handleClick() {console.log(params, paramsparamsparamsparamsparamsparams);params.onClick(111)setCount(count 1…

猫罐头怎么选择?精选的5款口碑好的猫罐头推荐!

猫罐头因其成分约80%为水分&#xff0c;对于不喜欢喝水的猫咪来说&#xff0c;正是可以用来补充水分的替代方案。 而近年来市面上也有越来越多讲究食用安全性的猫罐头&#xff0c;像是强调无添加多余加工品、或是不含谷物成分等的商品。但也因为种类过多&#xff0c;让铲屎官容…

k8s系列文章二:集群配置

一、关闭交换分区 # 临时关闭分区 swapoff -a # 永久\关闭自动挂载swap分区 sudo sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab 二、修改cgroup管理器 ubuntu 系统&#xff0c;debian 系统&#xff0c;centos7 系统&#xff0c;都是使用 systemd 初始化系统的。systemd 这边…

11-13 代理模式

调用者 代理对象 目标对象 代理对象除了可以完成核心任务&#xff0c;还可以增强其他任务,无感的增强 代理模式目的: 不改变目标对象的目标方法的前提,去增强目标方法 分为:静态代理,动态代理 静态代理 有对象->前提需要有一个类&#xff0c;那么我们可以事先写好一个类&a…

擎创动态 | 再获上海区政府肯定,擎创科技被评为年度优秀高新技术企业

11月6日&#xff0c;上海市静安区副区长张慧和市北高新集团总裁陈军一行来到擎创科技调研指导&#xff0c;由擎创科技高管张健和陈莹陪同交流。 陈莹女士首先向副区长一行详细介绍了擎创科技的发展现状、落地实践效益以及未来的规划布局。在公司的成长过程中&#xff0c;得到静…

【Unity】 场景优化策略

Unity 场景优化策略 GPU instancing 使用GPU Instancing可以将多个网格相同、材质相同、材质属性可以不同的物体合并为一个批次&#xff0c;从而减少Draw Calls的次数。这可以提高性能和渲染效率。 GPU instancing可用于绘制在场景中多次出现的几何体&#xff0c;例如树木或…

腾讯云优惠服务器有哪些?腾讯云服务器优惠券领取入口汇总

腾讯云此次推出云服务器中最实惠的2核2G服务器以每年仅需88元的超低价格为用户提供稳定可靠的计算资源。这样的价格对于个人网站、小型企业以及学生开发者来说绝对是一笔难以忽视的优惠。 腾讯云双十一领9999代金券 https://1111.mian100.cn 腾讯云新用户领2860代金券 https:…

快速拉取聚水潭单据的ETL工具

聚水潭介绍 聚水潭平台则是国内较为出名的电商ERP平台&#xff0c;为企业提供了便捷的销售和管理服务&#xff0c;专注于提高交易效率&#xff0c;但是如何将数据快速同步到其他系统一直是很多企业的痛点。 ETLCloud数据集成平台提供了丰富的数据分析工具和算法模型&#xff…

Nat. Med. | 成年人的城市生活环境对心理健康的影响

今天为大家介绍的是来自Jiayuan Xu和Gunter Schumann团队的一篇论文。城市居民暴露于许多可能相互结合和相互作用的环境因素&#xff0c;这些因素可能影响心理健康。目前尚未有工作尝试建模城市生活的复杂实际暴露与大脑和心理健康之间的关系&#xff0c;以及这如何受遗传因素调…

js设置图片放大缩小拖动

效果: 思路: 在外层box进行相对定位relative,img设置绝对定位absolute;通过监听滚轮事件(wheel),设置样式缩放中心点(transformOrigin)和缩放转换(transform);获取到图片大小和位置,设置对应图片宽度高度和top、left偏移;鼠标按下事件(mousedown)和鼠标移动事…