【JavaEE】多线程之定时器(Timer)

简介: 【JavaEE】多线程之定时器(Timer)

1.定时器的定义

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

定时器是一种实际开发中非常常用的组件.类似于下方的场景就需要用到定时器:

  • 比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.
  • 比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).


2.标准库中的定时器

2.1构造方法


1688789866112.png


2.2成员方法


1688789879635.png


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

schedule方法参数列表: void schedule(TimerTask task, long delay)  

作用:在指定的延迟之后安排指定的任务执行。

实现一个简单的定时器使用:

import java.util.Timer;
import java.util.TimerTask;
public class testDemo1 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello4");
            }
        },4000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        },2000);
        System.out.println("hell01");
    }
}

上方代码,是让在定时器中执行4个任务,分别等待时间4s,3s,2s,1s,打印内容。

运行结果如下:



观察结果,定时器通过对延迟时间的排序完成了打印,再仔细看,程序并没有停止,什么时候程序不会停止呢?就是前台线程没有执行完的时候程序不会停止,也就是说定时器中的创建的线程是前台线程,并且在完成任务后,不会立即停止。


3.模拟实现一个定时器

定时器的主要构造内容:

public class MyTimer{

       //1.一个优先级阻塞队列

       //2.一个内部类Mytask(服务于schedule方法)

       //3.schedule方法

       //4.一个构造方法

       //5.一个锁

}  


定时器中核心数据结构是优先级阻塞队列(PriorityBlockingQueue),它能够帮助我们判断谁先执行,谁后执行。

schedule()方法

schedule()方法是定时器的核心方法,它相当于一个时间表,安排每一个任务的时间。

该方法有两个参数:1.runnable(任务) 2.delay(延迟时间)

因为需要将两个参数同时加入到 优先级阻塞队列(PriorityBlockingQueue)当中,因此我们需要将其进行封装一下,代码如下:

class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    public long time;
    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time =System.currentTimeMillis()+time;
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time-o.time);
    }
}

在使用延迟时间时,我们现在使用绝对时间来记录,这里使用到了时间戳,并且实现Comparable<>接口,他的目的是告诉阻塞队列,根据时间判断优先执行哪一个任务。

进行来看schedule方法的实现:

public void schedule(Runnable runnable,long delay) {
        MyTask task=new  MyTask(runnable,delay);
        queue.put(task);
        //来新任务了,就叫醒线程
        synchronized (locker){
            locker.notify();
        }
    }

代码解读是:构造一个任务,并且设定它的延迟时间,将任务加入到优先级阻塞队列,下方的notify方法配合构造方法中的wait使用(下方会有解读)。

构造方法

代码实现如下:

public MyTimer() {
        Thread t=new Thread(()->{
            while (true) {
                synchronized (locker) {
                    try {
                        MyTask cur = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime <= cur.time) {
                            //没到时间
                            queue.put(cur);
                            locker.wait(cur.time - curTime);
                        } else {
                            //时间到了
                            cur.runnable.run();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
           }
        });
        t.start();
    }

代码解读:

我们先取出队列的队首元素,在调用出现在的时间戳,判断队首元素是否达到时间了,因为优先级阻塞队列里面没有peek方法,我们每次比较都必须将队首元素拿出来,若没有达到指定时间,我们需要再将队首元素再次塞进队伍当中,塞回去之后,若时间到了,就执行这个任务的run方法。

在判断后,时间没到时,我们不能再一直进行判断时间,因为这个cpu即没干任何事,也没有得到休息(cpu忙等),这时我们就可以使用wait方法,让cpu先将资源释放了,等待唤醒就行了,只要参数中的时间到了或者有需要比现在任务优先级更高的任务需要执行,就让他先执行。


4.MyTimer完整代码

import java.util.concurrent.PriorityBlockingQueue;
//没个任务都有一个需要做的事
class MyTask implements Comparable<MyTask>{
    public Runnable runnable;
    long time;
    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time =System.currentTimeMillis()+time;
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time-o.time);
    }
}
public class MyTimer {
    //核心数据结构,优先级阻塞队列
    private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
    private 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) {
                synchronized (locker) {
                    try {
                        MyTask cur = queue.take();
                        long curTime = System.currentTimeMillis();
                        if (curTime <= cur.time) {
                            //没到时间
                            queue.put(cur);
                            locker.wait(cur.time - curTime);
                        } else {
                            //时间到了
                            cur.runnable.run();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
           }
        });
        t.start();
    }
}
相关文章
|
7天前
|
Java
【JavaEE】——多线程常用类
Callable的call方法,FutureTask类,ReentrantLock可重入锁和对比,Semaphore信号量(PV操作)CountDownLatch锁存器,
|
7天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
7天前
|
Java Go 调度
【JavaEE】——线程池大总结
线程数量问题解决方式,代码实现线程池,ThreadPoolExecutor(核心构造方法),参数的解释(面试:拒绝策略),Executors,工厂模式,工厂类
|
7天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
7天前
|
Java 调度
|
7天前
|
Java 调度
【JavaEE】——线程的安全问题和解决方式
【JavaEE】——线程的安全问题和解决方式。为什么多线程运行会有安全问题,解决线程安全问题的思路,synchronized关键字的运用,加锁机制,“锁竞争”,几个变式
|
7天前
|
Java API 调度
【JavaEE】——多线程(join阻塞,计算,引用,状态)
【JavaEE】——多线程,join,sleep引起的线程阻塞,多线程提升计算效率,如何获取线程的引用和状态
|
7天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
7天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
7天前
|
安全 前端开发 Java
【JavaEE】——线程的诞生(超详细、易理解)
进程对内存的管理,进程间的通信,进程的缺点,线程的概念和特点,进程和线程在内存中的分配方式,进程和线程的结合,进程和线程之间的关系,线程的缺点,有无线程的对比,线程的总结