【Java|多线程与高并发】定时器(Timer)详解

这篇具有很好参考价值的文章主要介绍了【Java|多线程与高并发】定时器(Timer)详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 前言

在Java中,定时器Timer类是用于执行定时任务的工具类。它允许你安排一个任务在未来的某个时间点执行,或者以固定的时间间隔重复执行。

在服务器开发中,客户端向服务器发送请求,然后等待服务器响应. 但服务器什么时候返回响应,并不确定. 但也不能让客户端一直等下去, 如果一直死等,就没有意义了. 因此通常客户端会通过定时器设置一个"等待的最长时间".
java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言

2. 定时器的基本使用

Java的标准库库中就给我们提供了一个定时器Timer类

可以看到Timer这个类在很多包里面都有,注意要选择java.util里的

java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言

其中在Timer类中有一个十分重要的方法- schedule()方法
java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言

形参:

  • task:要执行的任务,必须是TimerTask的子类,可以通过继承TimerTask类并重写run()方法来定义具体的任务逻辑。
  • time:指定任务执行的时间,类型为java.util.Date

当然一个Timer类中也可以执行设置多个任务.

示例:

public class Demo17 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("1s!");
            }
        },1000);
        
        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 timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言
仔细观察运行结果,会发现这个程序有些问题,为什么程序执行完了,进程没有退出呢?

是因为Timer内部需要一组线程来执行注册任务,这里的线程是前台线程,会影响进程的退出

3. 实现定时器

实现定时器,最主要的就是实现里面的schedule方法

class MyTask{
    // 要执行的任务
    private Runnable runnable;
    // 时间
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + time;
    }
}
public class MyTimer {
    public void schedule(Runnable runnable, long time){
        MyTask myTask = new MyTask(runnable,time);
    }
}

System.currentTimeMillis()是Java中的一个静态方法,用于获取当前时间的毫秒数。

描述一个任务,以及多久后执行定时器的第一步完成了

接下来就是如何让这个定时器能够管理多个任务,例如上述示例中输出1s,2s,3s的那个示例一样

关于如何管理这些任务,我们肯定是想让设置时间短的任务先执行,但是在设置任务时,不一定会按照时间从小到大的顺序去进行放入. 这时候就要使用到 优先级队列(PriorityQueue)

但是优先级队列并不是线程安全的, 在多线程环境下使用优先级队列可能会出现问题,我们可以使用阻塞队列
不要忘了,我们可以创建一个带有优先级的阻塞队列
java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言

将任务添加到阻塞队列中即可.

但是优先级队列的对象的类型必须是可比较的. 我们可以让Mytask实现Comparable接口,实现里面的compareTo方法. 比较的规则就是时间,时间小的优先级高.

接下来就要检查队首任务的时间是否到了,时间到了就要执行任务. 可以单独创建一个扫描线程来进行检查.

完整代码:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

class MyTask implements Comparable<MyTask>{
    // 要执行的任务
    private Runnable runnable;
    // 时间
    private long time;

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

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
        return (int) (this.time - o.time);
    }
}
public class MyTimer {
    private BlockingQueue<MyTask> blockingQueue = new PriorityBlockingQueue<>();

    public MyTimer() {
        Thread t = new Thread(()->{
           while(true){
               try {
                   MyTask myTask = blockingQueue.take();
                   // 当前时间是否大于等于要执行任务的时间
                   if (System.currentTimeMillis() >= myTask.getTime()){
                       // 时间到了 执行任务
                       myTask.getRunnable().run();
                   }else {
                       // 时间没到,再把任务放回阻塞队列中
                       blockingQueue.put(myTask);
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
    }

    public void schedule(Runnable runnable, long time) throws InterruptedException {
        MyTask myTask = new MyTask(runnable,time);
        blockingQueue.put(myTask);
    }
}

测试代码:

public class Demo18 {
    public static void main(String[] args) throws InterruptedException {
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2s");
            }
        },2000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1s");
            }
        },1000);

        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3s");
            }
        },3000);

    }
}

运行结果:
java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言
结果没有问题.

4. 优化上述的定时器代码

但仔细思考上述代码中还存在一个问题:
java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言
这里的条件是while(true),说明程序会一直进行这里的循环, 这也是"忙等"

"忙等"是指一个线程在等待某个条件满足时,不断地进行无效的循环检查,而不释放CPU资源给其他线程执行。这种方式会浪费CPU资源,并且可能导致性能下降。

针对这个问题,我们可以使用 wait和notify来解决这个问题

通过使用waitnotify,对MyTimer这个类进行优化:

public class MyTimer {
    private BlockingQueue<MyTask> blockingQueue = new PriorityBlockingQueue<>();

    private Object locker = new Object();
    public MyTimer() {
        Thread t = new Thread(()->{
           while(true){
               try {
                   MyTask myTask = blockingQueue.take();
                   // 当前时间是否大于等于要执行任务的时间
                   if (System.currentTimeMillis() >= myTask.getTime()){
                       // 时间到了 执行任务
                       myTask.getRunnable().run();
                   }else {
                       // 时间没到,再把任务放回阻塞队列中
                       blockingQueue.put(myTask);
                       // 进行等待
                       synchronized (locker) {
                           locker.wait(myTask.getTime()-System.currentTimeMillis());
                       }
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
    }

    public void schedule(Runnable runnable, long time) throws InterruptedException {
        MyTask myTask = new MyTask(runnable,time);
        blockingQueue.put(myTask);
        synchronized (locker) {
            locker.notify();
        }
    }
}

虽然解决了"忙等"问题,但是又带来了新的问题.

如果扫描线程再取出队首任务(10分钟后要执行)时,线程切换,执行schedule方法,新增任务(5分钟后执行)然后执行notify,但此时并没有通知线程并没有意义,因为扫描线程刚执行完take,并没有执行到wait,然后扫描线程继续执行,进行wait,等待10分钟. 这样就会把刚才新增的5分钟后执行的任务给错过了.

对于上述问题 产生的原因还是因为"锁"的粒度不够大, 这些操作不是原子的,只需放大锁的粒度即可

public class MyTimer {
    private BlockingQueue<MyTask> blockingQueue = new PriorityBlockingQueue<>();

    private Object locker = new Object();
    public MyTimer() {
        Thread t = new Thread(()->{
           while(true){
               try {
                   synchronized (locker) {
                       MyTask myTask = blockingQueue.take();
                       // 当前时间是否大于等于要执行任务的时间
                       if (System.currentTimeMillis() >= myTask.getTime()){
                           // 时间到了 执行任务
                           myTask.getRunnable().run();
                       }else {
                           // 时间没到,再把任务放回阻塞队列中
                           blockingQueue.put(myTask);
                           // 进行等待
                           locker.wait(myTask.getTime()-System.currentTimeMillis());
                       }
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
    }

    public void schedule(Runnable runnable, long time) throws InterruptedException {
        MyTask myTask = new MyTask(runnable,time);
        blockingQueue.put(myTask);
        synchronized (locker) {
            locker.notify();
        }
    }
}

java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言
上述为了解决"忙等"问题,使用wait和notify进行优化,而在优化过程因为synchronized加锁的范围不一样,又带来了新的问题. 因此多线程问题很复杂,加锁的范围,线程的切换都会影响程序的执行效果.

5. 总结

文章主要介绍了定时器的基本使用,以及自定义实现定时器,实现一个定时器并不难.但如果要想将定时器实现的更好,也不是一件容易的事. 毕竟多线程环境中,很容易出现各种意想不到的问题.
java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言

感谢你的观看!希望这篇文章能帮到你!
专栏: 《从零开始的Java学习之旅》在不断更新中,欢迎订阅!
“愿与君共勉,携手共进!”
java timer,从零开始的Java学习之旅,# 多线程与高并发的那些事,java,jvm,开发语言文章来源地址https://www.toymoban.com/news/detail-731191.html

到了这里,关于【Java|多线程与高并发】定时器(Timer)详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python中的定时器用法:Timer定时器和schedule库

    目录 一、引言 二、Timer定时器 1、Timer定时器的原理 2、Timer定时器的使用方法 3、Timer定时器的实际应用案例 三、schedule库 1、schedule库的原理 2、schedule库的使用方法 3、schedule库的实际应用案例 四、Timer定时器和schedule库的比较 1、功能差异 2、适用场景 五、实际应用案例 六、

    2024年01月16日
    浏览(74)
  • GD32系列笔记六:定时器Timer

    目录 一、定时器的作用 二、定时器介绍 三、定时器配置 1. 用作封装延时函数,提高程序实时性; 2. 测试某段代码的执行时间; 3. 一些外设的核心,如PWM输入捕获、输出比较等。 1.时钟树 2.结构图(基本定时器为例)  TIMER_CK就是CK_TIMER    3. 工作原理               1. 

    2024年02月13日
    浏览(42)
  • Jmeter之同步定时器(Synchronizing Timer)

    同步定时器类似LoadRunner的集合点,作用是阻塞线程,达到指定的线程数量后,再一起释放。 添加定时器同步定时器(Synchronizing Timer) 1、模拟用户组的数量:每次释放的线程数量,即 并发数。 默认为0 设置为0则并发数等于线程租中的线程数;设置大于0则等待达到这个数量

    2024年02月11日
    浏览(56)
  • TM4C123系列(五)————timer定时器(timer模式)

    一.实验简介 通过定时器的timer模式来计时实现以1s为间隔将LED翻转。 二.板载定时器介绍 TM4C有两种定时器,一种为16/32bit的,一种是32/64bit的,两种定时器各有六个,对于每个定时器来说,它可以单独以较大的bit位作为一个定时器工作,也可以拆分为两个较小的bit位的定时器

    2024年02月15日
    浏览(43)
  • java多线程之定时器

    定时功能在java中主要是通过 Timer 类实现,因为它在内部还是使用多线程的方式进行处理,所以和多线程技术还是有非常大的管理 在JDK库中Timer类主要负责计划任务的功能,也就是在指定时间开始执行某一个任务,Timer类的主要功能就是设置计划任务,封装任务的类确是Timer

    2024年02月05日
    浏览(48)
  • 物联网操作系统-软件定时器(software timer)

    软件定时器就是\\\"闹钟\\\",你可以设置闹钟, ⚫ 在 30 分钟后让你起床工作 ⚫ 每隔 1 小时让你例行检查机器运行情况 软件定时器也可以完成两类事情: ⚫ 在\\\"未来\\\"某个时间点,运行函数 ⚫ 周期性地运行函数 日常生活中我们可以定无数个\\\"闹钟\\\",这无数的\\\"闹钟\\\"要基于一个真实

    2024年02月21日
    浏览(44)
  • Java多线程案例之定时器

    定时器是一种实际开发中非常常用的组件, 类似于一个 “闹钟”, 达到一个设定的时间之后, 就执行某个指定好的代码. 比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连. 比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除). 类似于这样的场景就需要

    2024年02月20日
    浏览(49)
  • 【Java|多线程与高并发】线程池详解

    Java线程池是一种用于管理和重用线程的机制,它可以在需要执行任务时,从线程池中获取线程,执行任务,然后将线程放回池中,以便后续使用。线程池可以有效地管理线程的数量,提高程序的性能和资源利用率。 为什么从线程池里面取线程比直接创建线程快呢? 创建线程是

    2024年02月11日
    浏览(46)
  • C#里面的三种定时计时器:Timer

    在.NET中有三种计时器: 1、System.Windows.Forms命名空间下的Timer控件,它直接继承自Componet。Timer控件只有绑定了Tick事件和设置Enabled=True后才会自动计时,停止计时可以用Stop()方法控制,通过Stop()停止之后,如果想重新计时,可以用Start()方法来启动计时器。Timer控件和它所在的

    2024年02月07日
    浏览(41)
  • 【Java | 多线程案例】定时器的实现

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家在评论区交流讨论💌 Java中, Timer类 是用于计划和执行重复任务的类( Java标准

    2024年02月03日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包