一、前言
业务需要有部分是需要读取缓存文件,因为文件的读写有用到FileLock的独占锁,高并发下,可能出现锁一直获取失败的情况,需要有一个读取超时的机制,这里使用Timer简单实现一个计时控制器的需求。
二、实现代码段
1.计时进程类
因为有类似关闭资源的动作,所以我实现了AutoCloseable。
- package com.cn.util;
-
- import java.util.Timer;
- import java.util.TimerTask;
- import java.util.concurrent.atomic.AtomicBoolean;
-
- /**
- * 计时进程
- * 进程有ture正在运行和false终止两种状态
- * 进程提供获取状态方法,在第一次获取计时器状态时初始化计时器,并在构造入参的过期时间后终止计时器
- * 计时进程可以主动结束
- */
- public class TimerProcess implements AutoCloseable{
-
- /**
- * 计时器对象
- */
- private Timer timer;
-
- /**
- * 过期时间 秒
- */
- private int expireTimeSecond;
-
- /**
- * 原子 boolean 状态
- */
- private AtomicBoolean atomicBoolean;
-
- /**
- *
- * @param expireTimeMil 毫秒
- */
- public TimerProcess(int expireTimeMil){
- this.expireTimeSecond = expireTimeMil * 1000;
- this.atomicBoolean = new AtomicBoolean(true);
- }
-
- /**
- * 获取计时器状态
- * @return
- */
- public boolean getState(){
- if(timer == null)
- timer = new Timer(true);
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- atomicBoolean.set(false);
- }
- },this.expireTimeSecond);
- return this.atomicBoolean.get();
- }
-
-
- @Override
- public void close() throws Exception {
- this.timer.cancel();
- }
- }
2.调用代码
因为TimerProcess和文件资源都继承了Autocloseable接口,所以直接在try()中处理了资源关闭的问题。
- //测试缓存文件
- File cacheFile = new File("");
- //10秒过期为例
- try(TimerProcess timerProcess = new TimerProcess(10);
- RandomAccessFile raf = new RandomAccessFile(cacheFile,"rw");
- FileChannel fc = raf.getChannel();){
- while (timerProcess.getState()){
- FileLock fl = fc.tryLock();
- if(fl == null)
- TimeUnit.SECONDS.sleep(1);
- else{
- System.out.println("get cache file channel success.");
- //后续的业务
- }
- }
- }
该实现还是会存在毫秒级的误差,因为在获取FileLock失败后到再次获取计时器的状态存在毫秒级的时间间隔,满足我的业务需求,各位有需要可以自己优化。
三、守护线程
在开发过程中,又遇到一个现象,代码如下。按照预先设计的逻辑来说,while 判断参数会在1秒之后get到false而跳出循环,然后打印end,终止主函数,但实际主程序依然挂起,jvm并没有关闭。
因为Timer在调用构造时,如果没有特殊说明,是以用线程存在的,这也是为什么在计时器类中,需要使用new Timer(true);这是声明计时器对象为守护线程。
- public static void main(String[] args) throws InterruptedException {
- Timer timer = new Timer();
- AtomicInteger atomicInteger = new AtomicInteger(1);
- AtomicBoolean atomicBoolean = new AtomicBoolean(true);
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println("timer task run.");
- atomicBoolean.set(false);
- }
- },1000);
-
- while (atomicBoolean.get()){
- TimeUnit.MILLISECONDS.sleep(100);
- }
- System.out.println("end");
- }
什么是守护线程?
JAVA常见的线程分为两种,用户线程和守护线程。
用户线程 一般指工作线程,用来实现具体的业务操作,比如Main方法、继承Thread的线程类等等。该线程是程序的主要组成架构,当用户线程存在时,存续会持续运行。
守护线程 一般指服务线程,用来实现系统的服务、监控操作,比如JVM GC,JIT等等。该线程为程序非必须部分,顾名思义,是为了守护而存在的线程,服务、监控都需要一个对应的对象,不然毫无意义,所以当程序只有守护线程存在时,程序就会终止。
所以这里实现的计时器,其实也是一种守护线程的存在,当main函数执行完之后,应该终止程序。
四、如何声明线程类型
Timer类就提供了boolean类型的构造方法,来申明是守护/用户线程。
而一般的线程对象,也有setDaemon()方法来设置为用户线程或者守护线程,需要注意,该方法需要在线程启动之前调用,否则会造成程序异常。默认实例化的线程都为用户线程。