Java多线程案例【定时器】

  • 🍒一.什么是定时器
  • 🍒二.标准库中的定时器(timer)
  • 🍎2.1什么是定时器
  • 🍎2.2定时器的使用
  • 🍒三.实现定时器
  • 🍎3.1什么是定时器
  • 🍎3.2最终实现代码


🍒一.什么是定时器

定时器也是软件开发中的一个重要组件. 类似于一个 “闹钟”. 达到一个设定的时间之后, 就执行某个指定好的代码

定时器是一种实际开发中非常常用的组件,我们举几个例子:

1.比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连
2.比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除)
以上类似于这样的场景就需要用到定时器

java 多线程定时器为什么会冲突 java多线程计时器_数据结构

🍒二.标准库中的定时器(timer)

🍎2.1什么是定时器

标准库中供了一个 Timer 类. Timer 类的核心方法为 schedule ,schedule 包含两个参数. 第一个参数指定即将要执行的任务代码TimerTask, 第二个参数指定多长时间之后执行 (单位为毫秒).

Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello");
}
}, 3000);

🍎2.2定时器的使用

Timer的构造方法

构造方法

说明

public Timer()

无参数构造方法,默认定时器关联的线程不是守护线程,线程名字也是默认值

public Timer(boolean isDaemon)

指定定时器中关联的线程是否为守护线程,如果是,参数为true

public Timer(String name)

指定定时器关联线程名称,线程类型默认为非守护线程

public Timer(String name, boolean isDaemon)

指定定时器关联线程名和线程类型

Timer方法

方法

说明

public void schedule (TimerTask task, long delay)

指定任务,延迟多久执行该任务

public void schedule(TimerTask task, Date time)

指定任务,指定任务的执行时间

public void schedule(TimerTask task, long delay, long period)

连续执行指定任务,延迟时间,连续执行任务的时间间隔,毫秒为单位

public void schedule(TimerTask task, Date firstTime, long period)

连续执行指定任务,第一次任务的执行时间,连续执行任务的时间间隔

public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

连续执行指定任务,第一次任务的执行时间,连续执行任务的时间间隔

public void scheduleAtFixedRate(TimerTask task, long delay, long period)

连续执行指定任务,延迟时间,连续执行任务的时间间隔,毫秒为单位

public void cancel()

终止定时器所有任务,终止执行的任务不受影响

TimerTask是专门来实现Runnable接口的
下面我们会实现一下定时器,我们就不用TimerTask了,我们直接使用Runnable,因为TimerTask实现了Runnable接口,所以后面测试我们自己所写的schedule方法时,也可以传入TimerTask类型的引用,既然是简单地实现,那就不实现连续执行的功能了。.

public class Test {
    public static void main(String[] args){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行线程在5s后执行");
            }
        },5000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行线程在2s后执行");
            }
        },2000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行线程在3s后执行");
            }
        },3000);
    }
}

java 多线程定时器为什么会冲突 java多线程计时器_java_02

🍒三.实现定时器

🍎3.1什么是定时器

定时器的构成:一个带优先级的阻塞队列

为啥要带优先级呢?
因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的. 使用带优先级的队列就可以高效的把这个 delay 最小的任务找出来.

1.队列中的每个元素是一个 Task 对象,Task 中带有一个时间属性, 队首元素就是即将同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

class MyTask implements Comparable<MyTask>{
    //执行的时间戳
    private long time;
    //接受具体任务
    private Runnable runnable;
    //创建MyTask构造方法
    public MyTask(Runnable runnable,long time) {
        //通过currentTimeMillis来获取time 中存的是绝对时间, 超过这个时间的任务就应该被执行
        this.time = System.currentTimeMillis()+time;
        this.runnable = runnable;
    }
    //执行任务
    public void run(){
        this.runnable.run();
    }
    //提供对外time
    public long getTime() {
        return time;
    }
    //执行comparable接口来进行时间的比较,并将time的long类型转换为int类型
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time-o.time);
    }
}
  1. Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象.通过 schedule 来往队列中插入一个个 Task 对象.
class MyTimer{
    // 定时器内部要能够存放多个任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //为锁创建一个对象
    Object locker = new Object();
    public void schedule(Runnable runnable, long delay) {
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        // 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
        synchronized (locker) {
            locker.notify();
        }
    }
  1. Timer 类中存在一个 worker 线程, 一直不停的扫描队首元素, 看看是否能执行这个任务.所谓 “能执行” 指的是该任务设定的时间已经到达了
class MyTimer{
    // 定时器内部要能够存放多个任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    //为锁创建一个对象
    Object locker = new Object();
    public void schedule(Runnable runnable, long delay) {
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        // 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
        synchronized (locker) {
            locker.notify();
        }
    }
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    // 先取出队首元素
                    MyTask task = queue.take();
                    // 再比较一下看看当前这个任务时间到了没?
                    long curTime = System.currentTimeMillis();
                    if (curTime < task.getTime()) {
                        // 时间没到, 把任务再塞回到队列中.
                        queue.put(task);
                        // 指定一个等待时间,防止有的线程需要等待时间很长,但是线程一直运行等待时间到来执行,这样会占有CPU占有资源
                        synchronized (locker) {
                            locker.wait(task.getTime() - curTime);
                        }
                    } else {
                        // 时间到了, 执行这个任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

🍎3.2最终实现代码

package thread;

import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;

// 创建一个类, 表示一个任务.
class MyTask implements Comparable<MyTask> {
    // 任务具体要干啥
    private Runnable runnable;
    // 任务具体啥时候干. 保存任务要执行的毫秒级时间戳
    private long time;

    // after 是一个时间间隔. 不是绝对的时间戳的值
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }

    public void run() {
        runnable.run();
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
        // 到底是谁见谁, 才是一个时间小的在前? 需要咱们背下来.
        return (int) (this.time - o.time);
    }
}

class MyTimer {
    // 定时器内部要能够存放多个任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long delay) {
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        // 每次任务插入成功之后, 都唤醒一下扫描线程, 让线程重新检查一下队首的任务看是否时间到要执行~~
        synchronized (locker) {
            locker.notify();
        }
    }

    private Object locker = new Object();

    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    // 先取出队首元素
                    MyTask task = queue.take();
                    // 再比较一下看看当前这个任务时间到了没?
                    long curTime = System.currentTimeMillis();
                    if (curTime < task.getTime()) {
                        // 时间没到, 把任务再塞回到队列中.
                        queue.put(task);
                        // 指定一个等待时间
                        synchronized (locker) {
                            locker.wait(task.getTime() - curTime);
                        }
                    } else {
                        // 时间到了, 执行这个任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

public class Test {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello timer!");
            }
        }, 3000);
        System.out.println("main");
    }
}