Java多线程案例【定时器】

简介: Java多线程案例【定时器】

🍒一.什么是定时器


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


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


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



cb5ed5597888467faaf56c0a333da019.png


🍒二.标准库中的定时器(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方法


image.png

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);
    }
}

9bdac67ab25c4772baa9419081f31343.png

🍒三.实现定时器


🍎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");
    }
}


相关文章
|
4天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
4天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
|
4天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
26 1
|
12天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
11天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
7月前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【2月更文挑战第22天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个主题,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。
62 0
|
7月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
4月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
66 1
|
5月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
97 0
|
6月前
|
安全 Java 开发者
Java并发编程中的线程安全策略
在现代软件开发中,Java语言的并发编程特性使得多线程应用成为可能。然而,随着线程数量的增加,如何确保数据的一致性和系统的稳定性成为开发者面临的挑战。本文将探讨Java并发编程中实现线程安全的几种策略,包括同步机制、volatile关键字的使用、以及java.util.concurrent包提供的工具类,旨在为Java开发者提供一系列实用的方法来应对并发问题。
48 0