徐闻网站建设公司,wordpress缩略图代码,百度网站优化外包,手机购物网站源码前言 逆水行舟#xff0c;不进则退#xff01;#xff01;#xff01; 目录 什么是定时器 实现一个定时器
自己实现一个定时器 什么是线程池 线程池的使用#xff1a;
什么是工厂模式#xff1f;
自己实现一个线程池#xff1a; 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方法的第一个参数// 其实就是RunnableTimerTask就是一个实现了Runnable的抽象类。// 也要通过run() 方法来描述一段代码。Overridepublic void run() {System.out.println(运行定时器任务3);}}, 3000); // TimerTask 的第二个参数是指定的时间// 意思就是一段时间后触发第一个参数描述的代码。// 多写两个感受一下定时执行的任务timer.schedule(new TimerTask() { Overridepublic void run() {System.out.println(运行定时器任务2);}}, 2000); timer.schedule(new TimerTask() { Overridepublic 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 PriorityBlockingQueueMyTask 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 ComparableMyTask {//要执行的任务内容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();}Overridepublic int compareTo(MyTask o) {// 返回 小于0 大于0 0 这三个数字// this 比 o 大 返回 0return (int)(this.time - o.time);}
}public class ThreadDemo11 {public static void main(String[] args) {MyTimer myTimer new MyTimer();myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(任务1);}}, 1000);myTimer.schedule(new Runnable() {Overridepublic void run() {System.out.println(任务2);}}, 2000);}
} 注意 1在创建一个新任务那里在将任务创建好放入堆中之后有一步唤醒操作这里的唤醒操作是唤醒扫描线程那里的等待。在扫描线程中会将最优先的任务拿出来看看如果还没到执行的时间那就再将任务放回到优先级队列中并且阻塞需要等待的时间。就是这里如果在阻塞等待时间内又来了一个优先级更高的任务时间更短比刚刚阻塞等待的时间还短就需要扫描线程重新去优先级队列中拿出最优先的任务重新计算阻塞等待的时间然后阻塞等待。所以说这里的这个唤醒机制很有必要。 2在扫描线程中synchronizedthis 这行代码 锁住的是实现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() {Overridepublic 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 BlockingQueueRunnable 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() {Overridepublic 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. unitkeepAliveTime的时间单位。 5. workQueue任务队列用于存放待执行的任务。 6. threadFactory创建新线程的工具类。 7. handler当线程池中的线程数量超过maximumPoolSize且任务队列已满时如何处理新提交的任务。 注解 1corePoolSize 核心线程数 和 maximumPoolSize 最大线程数 的区别 核心线程数是指线程池中一直保持的线程数哪怕它们处于空闲状态。这意味着即使线程池中没有任务这些核心线程也会一直保持存在以便于快速响应新的任务请求 最大线程数则是指线程池中允许的最大线程数这包括了空闲线程和正在工作的线程。如果线程池的任务队列已满且所有的核心线程都在工作那么此时如果有新任务提交线程池会根据设置的策略决定是否创建新的线程 举两个例子 1在一家公司中 核心线程数就是正式员工 最大线程数就是 正式员工 实习生当所有的正式员工都在忙碌并且还有新的任务下来这时就会招收一些实习生来缓解压力。如果长时间任务比较少实习生一直在摸鱼线程空闲那么就会销毁这个线程但是呢核心线程数并不会被销毁即使线程空闲 2假设你开了一家餐厅这家餐厅的服务员就是线程而顾客就是任务。核心线程数就好比是你固定的服务员人数无论餐厅是否忙碌这些服务员都会在岗位上待命随时准备为顾客服务。 最大线程数则好比是餐厅能够容纳的最大服务员数量包括正在工作的和待命的。如果所有的服务员都在工作并且还有新的顾客进来那么就需要根据餐厅的规定来决定是否需要再雇佣新的服务员。 例如你的餐厅规定当所有服务员都在工作时如果有新的顾客进来那么就不能再接待更多的顾客了除非有服务员完成他们的工作并腾出位置。这就是最大线程数的作用。 2keepAliveTime 简单解释就是 实习生可以摸鱼的最大时间超过这个线程就销毁。unit: keepAliveTime 是摸鱼时间的时间单位 3handler 其实就是一个拒绝策略 ThreadPoolExecutor类的构造方法中处理提交任务超过线程池最大容量的拒绝策略有四种 1 AbortPolicy默认直接抛出RejectedExecutionException异常阻止系统正常运行。 2 DiscardOldestPolicy丢弃等待队列中最旧的任务然后重新尝试执行任务重复此过程直到能够执行任务为止。 3 DiscardPolicy直接丢弃新来的任务不抛出异常。 4 CallerRunsPolicy让调用者自己运行任务。 我是专注学习的章鱼哥~