多线程(JavaEE初阶系列5)

目录

前言:

1.什么是定时器

2.标准库中的定时器及使用

3.实现定时器

结束语:


前言:

在上一节中小编给大家介绍了多线程中的两个设计模式,单例模式和阻塞式队列模式,在单例模式中又有两种实现方式一种是懒汉模式,一种是饿汉模式,在这两种模式中我们推荐大家使用的是懒汉模式,虽然饿汉模式是天然的线程安全的,但是与饿汉模式相比起来效率没有懒汉模式的高。在阻塞式队列中给大家重点提到了生产者和消费者模型,这个是我们以后会经常用到的一种模式,当时小编为了大家好理解给大家举了两个例子一个是包饺子,一个就是三峡大坝的削峰填谷,希望大家重点理解这两个例子。这节中小编将给大家讲解一下多线程中的定时器,讲解一下什么是定时器,定时器的使用以及手动实现一个定时器。

1.什么是定时器

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

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

2.标准库中的定时器及使用

在标准库中提供了一个类:Timer类。

Timer timer = new Timer( );

Timer类的核心方法为schedule。

  • schedule包含了两个参数,第一个参数指定即将要执行的任务代码,第二个参数指定多长时间之后执行(单位为毫秒)。

timer.schedule( new TimerTack( ) {

        @Override

        public void run() {

                System.out.println("hello");

        }

} , 3000 );

下面我们就在idea中来给大家具体演示一下:

代码展示:

package Time;

import java.util.Timer;
import java.util.TimerTask;

public class ThreadDemo1 {
    public static void main(String[] args) {
        //创建一个定时器
        Timer timer = new Timer();
        //让hello4、hello3、hello2、hello1在线程启动之后分别在4s、3s、2s、1s之后执行。
        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);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        },1000);
        System.out.println("hello0");
    }
}

结果展示:

多线程(JavaEE初阶系列5)_第1张图片

3.实现定时器

要想实现一个定时器我们就需要先来了解一下定时器的构成。

定时器的构成:

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

这里给大家解释一下为啥要带优先级呢?

因为阻塞式队列中的任务都有各自执行时刻(delay),最先执行的任务一定是delay最小的,使用优先级的队列就可以高效的把这个delay最小的任务找出来了。所以这里的核心数据结构是“堆”!!!之前学习数据结构中的PriorityQueue就是一个带优先级的阻塞式队列。

注:具体的操作步骤请详细看代码内的注释!!!

代码展示:

package Time;

import java.util.PriorityQueue;
class MyTask implements Comparable{
    public Runnable runnable;
    //为了方便后续的判定,使用绝对的时间戳
    public long time;
    public MyTask(Runnable runnable, long delay) {
        this.runnable = runnable;
        //取当前时刻的时间戳 + delay,作为该任务实际执行的时间戳。
        this.time = System.currentTimeMillis() + delay;
    }

    //指定一下在后续的优先级队列中我们是要按照时间来进行比较大小
    @Override
    public int compareTo(MyTask o) {
        //这样的写法意味着每次取出的是时间最小的元素
        return (int) (this.time - o.time);
    }
}
//自己实现一个类似于Timer类的MyTimer
class MyTimer{
    //这个结构要求带有优先级的阻塞队列,核心数据结构就是“堆”。
    //PriorityQueue<> ———— <>里面的元素需要我们手动的封装一下,创建一个MyTask类,表示两方面的信息。1.执行的任务是啥。2.任务啥时候执行。
    private PriorityQueue queue = new PriorityQueue<>();

    //创建一个锁对象
    private Object locker = new Object();

    //此处的delay是一个形如3000这样的数字(指多长时间后执行该任务)
    public void schedule(Runnable runnable, long delay) {
        //根据参数,构造MyTask,插入队列即可。
        synchronized (locker) {
            synchronized (locker) {
                MyTask myTask = new MyTask(runnable, delay);
                queue.offer(myTask);
                locker.notify();
            }
        }
    }

    //在这里构造线程,负责执行具体的任务
    public MyTimer() {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {
                        //阻塞队列,只有阻塞的入队列和阻塞的出队列,没有阻塞的查看队首元素。
                        while (queue.isEmpty()) {
                            locker.wait();
                        }
                        MyTask myTask = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime >= myTask.time) {
                            //时间到了,可以执行任务了
                            queue.poll();
                            myTask.runnable.run();
                        } else {
                            //时间还没到
                            locker.wait(myTask.time - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动线程
        t.start();
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        //创建一个定时器对象
        MyTimer myTimer = new MyTimer();
        //模仿之前的使用方式使用
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello4");
            }
        }, 4000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello3");
            }
        }, 3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello2");
            }
        }, 2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello1");
            }
        }, 1000);
        System.out.println("hello0");
    }
}

结果展示:

多线程(JavaEE初阶系列5)_第2张图片

可以看到上述代码的执行结果与标准库中定时器的效果一样。

结束语:

这节中小编带着大家一起了解了Java标准库中定时器的使用方式,并给大家实现了一下定时器。希望这节对大家学习JavaEE有一定的帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)

你可能感兴趣的:(JavaEE初阶,java-ee,java)