• Timer实现简单计时控制器,毫秒级误差,浅谈守护线程和用户线程


    一、前言

            业务需要有部分是需要读取缓存文件,因为文件的读写有用到FileLock的独占锁,高并发下,可能出现锁一直获取失败的情况,需要有一个读取超时的机制,这里使用Timer简单实现一个计时控制器的需求。

    二、实现代码段

            1.计时进程类

                    因为有类似关闭资源的动作,所以我实现了AutoCloseable。

    1. package com.cn.util;
    2. import java.util.Timer;
    3. import java.util.TimerTask;
    4. import java.util.concurrent.atomic.AtomicBoolean;
    5. /**
    6. * 计时进程
    7. * 进程有ture正在运行和false终止两种状态
    8. * 进程提供获取状态方法,在第一次获取计时器状态时初始化计时器,并在构造入参的过期时间后终止计时器
    9. * 计时进程可以主动结束
    10. */
    11. public class TimerProcess implements AutoCloseable{
    12. /**
    13. * 计时器对象
    14. */
    15. private Timer timer;
    16. /**
    17. * 过期时间 秒
    18. */
    19. private int expireTimeSecond;
    20. /**
    21. * 原子 boolean 状态
    22. */
    23. private AtomicBoolean atomicBoolean;
    24. /**
    25. *
    26. * @param expireTimeMil 毫秒
    27. */
    28. public TimerProcess(int expireTimeMil){
    29. this.expireTimeSecond = expireTimeMil * 1000;
    30. this.atomicBoolean = new AtomicBoolean(true);
    31. }
    32. /**
    33. * 获取计时器状态
    34. * @return
    35. */
    36. public boolean getState(){
    37. if(timer == null)
    38. timer = new Timer(true);
    39. timer.schedule(new TimerTask() {
    40. @Override
    41. public void run() {
    42. atomicBoolean.set(false);
    43. }
    44. },this.expireTimeSecond);
    45. return this.atomicBoolean.get();
    46. }
    47. @Override
    48. public void close() throws Exception {
    49. this.timer.cancel();
    50. }
    51. }

            2.调用代码

                    因为TimerProcess和文件资源都继承了Autocloseable接口,所以直接在try()中处理了资源关闭的问题。

    1. //测试缓存文件
    2. File cacheFile = new File("");
    3. //10秒过期为例
    4. try(TimerProcess timerProcess = new TimerProcess(10);
    5. RandomAccessFile raf = new RandomAccessFile(cacheFile,"rw");
    6. FileChannel fc = raf.getChannel();){
    7. while (timerProcess.getState()){
    8. FileLock fl = fc.tryLock();
    9. if(fl == null)
    10. TimeUnit.SECONDS.sleep(1);
    11. else{
    12. System.out.println("get cache file channel success.");
    13. //后续的业务
    14. }
    15. }
    16. }

    该实现还是会存在毫秒级的误差,因为在获取FileLock失败后到再次获取计时器的状态存在毫秒级的时间间隔,满足我的业务需求,各位有需要可以自己优化。

    三、守护线程

            在开发过程中,又遇到一个现象,代码如下。按照预先设计的逻辑来说,while 判断参数会在1秒之后get到false而跳出循环,然后打印end,终止主函数,但实际主程序依然挂起,jvm并没有关闭。

            因为Timer在调用构造时,如果没有特殊说明,是以用线程存在的,这也是为什么在计时器类中,需要使用new Timer(true);这是声明计时器对象为守护线程。

    1. public static void main(String[] args) throws InterruptedException {
    2. Timer timer = new Timer();
    3. AtomicInteger atomicInteger = new AtomicInteger(1);
    4. AtomicBoolean atomicBoolean = new AtomicBoolean(true);
    5. timer.schedule(new TimerTask() {
    6. @Override
    7. public void run() {
    8. System.out.println("timer task run.");
    9. atomicBoolean.set(false);
    10. }
    11. },1000);
    12. while (atomicBoolean.get()){
    13. TimeUnit.MILLISECONDS.sleep(100);
    14. }
    15. System.out.println("end");
    16. }

    什么是守护线程?

            JAVA常见的线程分为两种,用户线程和守护线程。

            用户线程 一般指工作线程,用来实现具体的业务操作,比如Main方法、继承Thread的线程类等等。该线程是程序的主要组成架构,当用户线程存在时,存续会持续运行。

            守护线程 一般指服务线程,用来实现系统的服务、监控操作,比如JVM GC,JIT等等。该线程为程序非必须部分,顾名思义,是为了守护而存在的线程,服务、监控都需要一个对应的对象,不然毫无意义,所以当程序只有守护线程存在时,程序就会终止。

    所以这里实现的计时器,其实也是一种守护线程的存在,当main函数执行完之后,应该终止程序。

    四、如何声明线程类型

            Timer类就提供了boolean类型的构造方法,来申明是守护/用户线程。

            而一般的线程对象,也有setDaemon()方法来设置为用户线程或者守护线程,需要注意,该方法需要在线程启动之前调用,否则会造成程序异常。默认实例化的线程都为用户线程。

  • 相关阅读:
    uni-fab彩色图标按钮
    【面试普通人VS高手系列】volatile关键字有什么用?它的实现原理是什么?
    天宇优配|多家房企发布再融资预案,最牛地产股九连板
    RabbitMQ - 消息堆积问题的最佳解决方案?惰性队列
    Emlog评论区显示用户操作系统与浏览器信息教程
    卷积、填充、步长;卷积神经网络的卷积核大小、个数,卷积层数如何确定呢;深度学习如何调参;
    Redis性能压测、监控工具及优化方案
    春雷视频添加投屏功能解惑
    R语言使用is.numeric函数判断数据对象是否是数值型
    Log4j 漏洞最早由阿里云团队发现;HashiCorp 挂牌上市,市值 152 亿美元;Go 1.18 Beta1 发布 | 开源日报
  • 原文地址:https://blog.csdn.net/Stepeh/article/details/125547429