JavaEE 初阶篇-深入了解定时器、工厂模式和比较器

本文详细介绍了如何在Java中使用定时器实现任务调度,包括定义MyTask任务类、使用优先级队列存储并按时间排序,以及使用简单工厂模式创建不同逻辑的实例。同时对比了Comparable与Comparator接口在比较对象方面的应用。

🔥博客主页: 【小扳_-优快云博客】
❤感谢大家点赞👍收藏⭐评论✍

文章目录

        1.0 定时器概述

        2.0 实现定时器

        2.1 实现定时器 - 定义 MyTask 任务类

        2.2 实现定时器 - MyTimer 定时器类存放任务的方法

        2.3 实现定时器 - MyTimer 定时器类读取任务的方法

        2.4 实现定时器的完整代码

        3.0 简单工厂模式概述

        4.0 深入了解比较器(Comparable 与 Comparator 区别)

        4.1 Comparable 接口

        4.2 Comparator 接口


        1.0 定时器概述

        定时器(Timer)是 Java 中用于执行定时任务的工具类,可以在指定的时间点执行某个任务,也可以按照一定的时间间隔重复执行任务。定时器提供了一种简单而有效的方式来安排任务的执行。

        2.0 实现定时器

        2.1 实现定时器 - 定义 MyTask 任务类

        定义一个任务类 MyTask ,成员变量有 Runnable 类型 runnable 变量long 类型的 time 变量。利用构造方法获取值即可。

        其中 this.time = time + System.currentTimeMillis() 换算成具体什么时间执行。

        该 Task 类需要实现 Comparable 接口重写 compareTo() 方法。是因为一旦有多个任务生成时,且将任务放入到容器中,比如说数组容器中,这就会导致需要不断的扫描数组中的任务,还有多长时间该执行了。为了解决这一弊端,直接把任务放入到优先级队列(小顶堆)中来,直接查看堆顶元素,因为堆顶元素剩余时间是最小的,如果堆顶元素还没到时间去执行任务,那么剩余的任务也一定还没到时间开始执行。最终,需要任务类 Task 需要实现内部比较器接口。

代码如下:

public class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    public long time;

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

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

}

        2.2 实现定时器 - MyTimer 定时器类存放任务的方法

        定义一个 MyTimer 定时器类,先要考虑的是任务需要存放在什么样的容器中,上面说到了需要用到优先级队列 PriorityQueue 数据结构。

    private final PriorityQueue<MyTask> queue = new PriorityQueue<>();

        MyTimer 类中的实例方法,再来实现将任务放入到容器。直接用 queue.offer() 方法,将任务对象放进 queue 容器中即可。不过需要考虑线程安全问题,当把任务放入到队列中这一个过程中,不允许有其他线程来读取任务,因此需要加锁处理。当任务已经传进队列完毕后,唤醒其他线程开始执行读取任务这个操作。

    public void schedule(Runnable runnable,long time){

        synchronized (queue){
            MyTask myTask = new MyTask(runnable,time);
            queue.offer(myTask);
            queue.notify();
        }
    }

        2.3 实现定时器 - MyTimer 定时器类读取任务的方法

        在 MyTime 实例对象创建之后,就可以尝试去读取队列中的任务了,因此在构造器中创建一个线程来读取队列中的任务

        当队列中的任务为空时,需要阻塞等待 wait() 方法,释放锁,再让当前线程进入阻塞等待。直到任务进来了,等到其他线程唤醒之后,当前线程才会去竞争锁,一旦竞争到锁之后,再判断队列中是否为空,此时不为空了,再从队列中查看堆顶任务,不能直接 poll() ,虽然队列中有任务了,但是时间到了吗?此时需要进一步验证,用当前的时间跟 queue.peek() 中的任务中的时间进行比较,一旦当前任务大于 queue.peek() 中的任务时间,就可以执行 runnable.run() 方法了,当然这个方法不需要我们手动去调用,这是一个回调函数,系统会自动调用的。

        还有一种情况:队列中不为空,且堆顶任务时间还没到,就需要释放锁之后继续阻塞等待 wait() 。但是这里需要死等吗?

        当然不能死等,万一没有任务进来了,没有人唤醒当前线程怎么办?所以不能一直死等,可以设置时间,时间一到,就可以重新竞争获取锁。

        但是对于队列为空这种情况就需要死等了,没有任务只能死等了。

        还有一个要注意的地方,可以换成 sleep() 方法等待吗?答案是不可以的,因为 sleep() 方法在等待的时候,是不会释放锁的,这就会导致万一有新任务进来的时候,锁一直被当前线程占用着。用 wait() 方法,可以提前释放锁等待,不会出现新任务进来线程一直被占用着的这种情况发生。还会提前唤醒当前线程去读取堆顶任务,万一顶堆任务更新了,就可以立马执行到位了。

        最后不要忘记进行线程启动。

代码如下:

    public MyTimer(){
        Thread thread = new Thread(()->{
           while (true){
               synchronized (queue){
                   if (queue.isEmpty()){
                       try {
                           queue.wait();
                       } catch (InterruptedException e) {
                           throw new RuntimeException(e);
                       }
                   }
                   MyTask myTask = queue.peek();
                   long curTime = System.currentTimeMillis();
                   if (myTask.time <= curTime){
                       //时间到了,考可以执行任务了
                       myTask.runnable.run();
                       queue.poll();
                   }else {
                       try {
                           queue.wait(myTask.time - curTime);
                       } catch (InterruptedException e) {
                           throw new RuntimeException(e);
                       }
                   }
               }
           }
        });
        thread.start();
    }

        2.4 实现定时器的完整代码

代码如下:

main 方法:

        定义了三个任务,分别定时为 1s 、20s 、3s

public class demo1 {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(()->{
            System.out.println(Thread.currentThread().getName() + "--> 1s 之后输出");
        },1000);

        myTimer.schedule(()->{
            System.out.println(Thread.currentThread().getName() + "--> 20s 之后输出");
        },20000);

        myTimer.schedule(()->{
            System.out.println(Thread.currentThread().getName() + "--> 3s 之后输出");
        },3000);
    }
}

MyTask 类:

public class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    public long time;

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

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

}

MyTimer 类:

import java.util.PriorityQueue;

public class MyTimer {

    private final PriorityQueue<MyTask> queue = new PriorityQueue<>();

    public void schedule(Runnable runnable,long time){

        synchronized (queue){
            MyTask myTask = new MyTask(runnable,time);
            queue.offer(myTask);
            queue.notify();
        }
    }

    public MyTimer(){
        Thread thread = new Thread(()->{
           while (true){
               synchronized (queue){
                   if (queue.isEmpty()){
                       try {
                           queue.wait();
                       } catch (InterruptedException e) {
                           throw new RuntimeException(e);
                       }
                   }
                   MyTask myTask = queue.peek();
                   long curTime = System.currentTimeMillis();
                   if (myTask.time <= curTime){
                       //时间到了,考可以执行任务了
                       myTask.runnable.run();
                       queue.poll();
                   }else {
                       try {
                           queue.wait(myTask.time - curTime);
                       } catch (InterruptedException e) {
                           throw new RuntimeException(e);
                       }
                   }
               }
           }
        });
        thread.start();
    }
}

运行结果:

        3.0 简单工厂模式概述

        工厂模式可以解决当设计构造方法中,参数列表一样,但是具体实现的逻辑不一样这种情况。这就需要用到工厂模式了。因为不能对构造方法进行重载,由于参数列表是完全一样,且构造方法的名字也是不能改变的。

        这就是遇到了参数列表相同,但是执行的逻辑不一样情况。 

        就可以通过静态方法来创建实例对象。

        在工厂模式中,可以通过不同的工厂方法或者不同的工厂类来创建对象,从而实现根据不同条件返回不同的实现对象。这种方式避免了构造方法重载的限制,让代码更加灵活和可扩展。

        通过一个工厂类来创建对象,客户端只需要传入对应的参数即可获得所需的对象。这种方式不符合传统意义上的工厂模式,但是仍然是一种创建对象的方式。 

举个例子:

public class Factory {

    private int a;
    private int b;
    
    public void setA(int a) {
        this.a = a;
    }

    public void setB(int b) {
        this.b = b;
    }

    public static Factory abFun(int a, int b){
        Factory factory = new Factory();
        factory.setA(a);
        factory.setB(b);
        return factory;
    }

    public static Factory baFun(int a,int b){
        Factory factory = new Factory();
        factory.setA(b);
        factory.setB(a);
        return factory;
    }
    
    public void print(){
        System.out.println(a - b);
    }

}

        4.0 深入了解比较器(Comparable 与 Comparator 区别)

        在Java中,有两种常见的比较对象的方式。

        4.1 Comparable 接口

        属于内部比较器 ,Comparable 接口是在对象自身的类中实现的,用于指定对象之间的自然顺序。一个类实现了 Comparable 接口后,就可以通过调用 compareTo() 方法来比较对象的顺序。只能对该类的对象进行排序,不能对其他类的对象进行排序。 

代码如下:

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    // 其他属性和方法

    @Override
    public int compareTo(Person otherPerson) {
        return this.age - otherPerson.age;
    }
}

        4.2 Comparator 接口

        属于外部比较器,是一个独立的比较器,可以用于对不同类的对象进行排序,而且可以定义多种不同的排序规则。通过实现 Comparator 接口并重写 compare() 方法,可以指定不同的比较规则。 

代码如下:

public class PersonComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.compareTo(p2);
    }
}

        这个外部比较器就比较灵活,没有固定写死。对于在类本身实现 Comparable 接口已经固定写死的。

        例如在使用 Collections.sort()方法对集合进行排序时可以传入自定义的比较器来指定排序规则。Collections.sort() 如果没有传入比较器,默认会用类本身实现的 comparable 接口中的comparaTo 方法。

 

// 创建一个比较器对象
Comparator<Person> personComparator = new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        // 自定义比较规则
        return Integer.compare(p1.getAge(), p2.getAge());
    }
};

// 创建一个包含Person对象的列表
List<Person> personList = new ArrayList<>();
personList.add(new Person("Alice", 25));
personList.add(new Person("Bob", 30));
personList.add(new Person("Charlie", 20));

// 使用Collections.sort()方法并传入比较器
Collections.sort(personList, personComparator);

// 打印排序后的列表
for (Person person : personList) {
    System.out.println(person.getName() + ": " + person.getAge());
}

评论 59
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小扳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值