JavaEE初阶第十二期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(十)

专栏:JavaEE初阶起飞计划

个人主页:手握风云

目录

一、多线程案例

[1.1. 定时器](#1.1. 定时器)


一、多线程案例

1.1. 定时器

定时器是软件开发的一个重要组件,是一种能够按照预设的时间间隔或在特定时间点执行某个任务或代码片段的机制。你可以把它想象成一个闹钟,只不过这个"闹钟"不是提醒你去起床,而是提醒计算机去执行某个特定的操作。定时器与阻塞队列一致,也会被单独封装成一个或一组服务器来使用。

  • 标准库中的定时器
java 复制代码
import java.util.Timer;
import java.util.TimerTask;

public class Demo1 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器执行任务 3000");
            }
        }, 3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器执行任务 2000");
            }
        }, 2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器执行任务 1000");
            }
        }, 1000);
        System.out.println("程序启动");
    }
}
  • 定时器的实现

对于自主实现的定时器,需要指定等待的最大时间,如果等不到,还需要执行其他的操作。

java 复制代码
class MyTimer {
    public MyTimer() {
    }

    //向定时器中添加任务
    public void schedule(Runnable runnable, long delay) {
        
    }
}

这里注意,TimerTask类实现的是Runnable接口,所以我们在schedule方法里面添加的也是Runnable类型的参数。既然要对任务进行组织管理,就得使用合适的数据结构,比如顺序表、栈。但是这些任务不一定是按照时间顺序添加的,并且添加的顺序和执行顺序没太大关系。如果使用顺序表,执行任务时,就需要遍历来找到时间最小的任务,效率太低。这时我们就可以使用堆来解决。

对于堆所存放的泛型参数,这里不能添加成Runnable,因为堆里面的任务不只是内容,还需要考虑任务的时间。

java 复制代码
class MyTimerTask {
    private Runnable task;
    // 这个地方为了和当前时间对比,确认任务是否执行,需要保存绝对的时间戳
    private long time;

    public MyTimerTask(Runnable task, long delay) {
        this.task = task;
        this.time = System.currentTimeMillis() + delay;
    }

    public Runnable getTask() {
        return task;
    }

    public long getTime() {
        return time;
    }
}

class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public MyTimer() {
    }

    //向定时器中添加任务
    public void schedule(Runnable runnable, long delay) {
        queue.offer(new MyTimerTask(runnable, delay));
    }
}

由于MyTimerTask是放在优先级队列中,所以我们还需要写出比较规则。

java 复制代码
class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable task;
    // 这个地方为了和当前时间对比,确认任务是否执行,需要保存绝对的时间戳
    private long time;

    public MyTimerTask(Runnable task, long delay) {
        this.task = task;
        this.time = System.currentTimeMillis() + delay;
    }

    public Runnable getTask() {
        return task;
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

接下来就是创建线程,让线程来检测任务是否到时间了,以及去执行这个任务。我们需要循环从队列中取出元素,判断是否到时间了,如果到达就出队列,没有就不做处理。

java 复制代码
import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable task;
    // 这个地方为了和当前时间对比,确认任务是否执行,需要保存绝对的时间戳
    private long time;

    public MyTimerTask(Runnable task, long delay) {
        this.task = task;
        this.time = System.currentTimeMillis() + delay;
    }

    public Runnable getTask() {
        return task;
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                if (queue.isEmpty()) {
                    continue;
                }
                MyTimerTask task = queue.peek();
                long curTime = System.currentTimeMillis();
                if (curTime < task.getTime()) {
                    continue;
                } else {
                    task.getTask().run();
                    queue.poll();
                }
            }
        });
        t.start();
    }

    //向定时器中添加任务
    public void schedule(Runnable runnable, long delay) {
        queue.offer(new MyTimerTask(runnable, delay));
    }
}

public class Demo2 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时任务:3000");
            }
        }, 3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时任务:2000");
            }
        }, 2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时任务:1000");
            }
        }, 1000);
    }
}

虽然执行效果没有什么问题,但上面的操作对于同一个队列进行出入,所以线程是不安全的。那我们就需要在入队列和出队列的操作里面都要进行加锁。第二个问题,就是忙等出现饿死。如上面的代码,当队列为空或者没到时间时,不做任何处理,只消耗CPU资源,没有任何实质性的进展。尤其是第二个continue,这里就相当于30分钟之后要去执行某项任务,每隔1分钟就得看一下时间,当我们设计了等待时间之后,到时间自动唤醒或者有优先级更高的任务要去执行。

完整代码:

java 复制代码
import java.util.PriorityQueue;

class MyTimerTask implements Comparable<MyTimerTask>{
    private Runnable task;
    // 这个地方为了和当前时间对比,确认任务是否执行,需要保存绝对的时间戳
    private long time;

    public MyTimerTask(Runnable task, long delay) {
        this.task = task;
        this.time = System.currentTimeMillis() + delay;
    }

    public Runnable getTask() {
        return task;
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimerTask o) {
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();
    private Object locker = new Object();
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {
                        if (queue.isEmpty()) {
                            locker.wait();
                        }
                        MyTimerTask task = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime < task.getTime()) {
                            locker.wait(task.getTime() - curTime);
                        } else {
                            task.getTask().run();
                            queue.poll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }

    //向定时器中添加任务
    public void schedule(Runnable runnable, long delay) {
        synchronized (locker) {
            queue.offer(new MyTimerTask(runnable, delay));
            locker.notify();
        }
    }
}

public class Demo2 {
    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时任务:3000");
            }
        }, 3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时任务:2000");
            }
        }, 2000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("定时任务:1000");
            }
        }, 1000);
    }
}
相关推荐
ankleless8 分钟前
C语言——关于指针(逐渐清晰版)
c语言·开发语言·指针·解引用操作符·c语言基础知识学习
打码农的篮球22 分钟前
STL——list
开发语言·c++·list
KNeeg_23 分钟前
Spring循环依赖以及三个级别缓存
java·spring·缓存
lkf1971130 分钟前
商品中心—1.B端建品和C端缓存
开发语言·后端·缓存
渣渣盟1 小时前
JavaScript核心概念全解析
开发语言·javascript·es6
AI_Gump1 小时前
【AI阅读】20250717阅读输入
java·spring boot·spring
java叶新东老师2 小时前
goland编写go语言导入自定义包出现: package xxx is not in GOROOT (/xxx/xxx) 的解决方案
开发语言·后端·golang
找不到、了2 小时前
Java排序算法之<插入排序>
java·算法·排序算法
设计师小聂!2 小时前
力扣热题100----------53最大子数组和
java·数据结构·算法·leetcode
檀越剑指大厂2 小时前
【Python系列】Flask 应用中的主动垃圾回收
开发语言·python·flask