【Java】一个标准的后台线程的封装

前言:

我们日常总会碰到这样的需求:

  • 把这个任务放到另外一个线程执行。
  • 我需要周期性执行任务的线程。那我们通常都是怎么解决这个问题呢?
  • 对于问题1,最简单的做法就是new thread,然后结束了。但在生产环境中,你常常会困扰,我这个线程到底执行没,或者是否正在执行。任务报错了日志在哪里呢?
  • 对于问题2,我们可能会选择定时器,ScheduledExecutor,但如果你想追踪你的线程运行情况,或者捕获异常,这都没法随心所欲

针对上面常见的两个问题,我自己封装了一个标准的后台线程模型

一、完整代码实现

/**
 * DefaultThread 后台标准线程
 */
public final class DefaultThread extends Thread {
    /** log */
    private static final Logger log = InternalLoggerFactory.getLogger(DefaultThread.class);
    
    /** 标准后台线程循环间隔时间 - 1分钟 */
    public static final long DEFAULT_INTERVAL = 60 * 1000;
    /** 标准后台线程循环最小间隔 - 300ms */
    public static final long MIN_INTERVAL = 300;
    
    /** 默认日志输出级别 -- debug */
    public static final LogLevel DEFAULT_LEVEL = LogLevel.DEBUG;
    
    private String name;
    private Runnable runnable;
    private boolean runOnce;
    private volatile boolean stop;
    private volatile long interval;
    private volatile LogLevel level = DEFAULT_LEVEL;
   
    /**
     * 构造函数
     * <p>将构造一个标准后台线程,每分钟运行1次
     * @param name
     * @param runnable
     */
    public DefaultThread(String name, Runnable runnable) {
        super(name);
        this.name = name;
        this.runnable = runnable;
        this.runOnce = false;
        this.stop = false;
        this.interval = DEFAULT_INTERVAL;
    }
    
    /**
     * 构造函数
     * <p>将构造一个标准后台线程
     * @param name
     * @param runnable
     * @param runOnce
     * @param interval
     */
    public DefaultThread(String name, Runnable runnable, boolean runOnce) {
        super(name);
        this.name = name;
        this.runnable = runnable;
        this.runOnce = runOnce;
        this.stop = false;
        this.interval = DEFAULT_INTERVAL;
    }
    
    /**
     * 构造函数
     * <p>将构造一个标准后台线程,指定间隔运行一次(不能小于最小间隔时间{@link #MIN_INTERVAL}})
     * @param name
     * @param runnable
     * @param runOnce
     * @param interval
     */
    public DefaultThread(String name, Runnable runnable, long interval) {
        super(name);
        this.name = name;
        this.runnable = runnable;
        this.runOnce = false;
        this.stop = false;
        this.interval = Math.max(MIN_INTERVAL, interval);
    }
    
    @Override
    public void start() {
        super.start();
    }

    public void stop() {
        try {
            this.interrupt();
        } catch (Exception e) {
            // Ignore
        }
        this.stop = true;
    }

    /**
     * 获得线程名称
     */
    public String getThreadName() {
        return this.name;
    }
    
    /**
     * 设置日志隔离级别
     */
    public void setLogLevel(LogLevel level) {
        this.level = level;
    }
    
    /**
     * 执行任务
     * @see java.lang.Thread#run()
     */
    public final void run() {
        if (runOnce) {
            runOnce();
            return;
        }
        
        while (!stop) {
            runOnce();
            
            try {
                sleep(interval);
            } catch (InterruptedException e) {
                // Ignore this Exception
            }
        }
    }

    /**
     * 执行一次
     */
    private void runOnce() {
        long startTime = 0;
        if (log.isLogEnabled(level)) {
            startTime = System.currentTimeMillis();
        }
        
        try {
            runnable.run();
        } catch (Throwable t) {
            log.error("thread run error [name:{}, runOnce:{}]", t, name, runOnce);
        }
        
        if (log.isLogEnabled(level)) {
            log.log(level, "{}#{}", (System.currentTimeMillis() - startTime));
        }
    }复制代码

二、代码解读

线程名称变成强制性
创建标准线程,必须指定线程名称,这是为了方便在jstack等工具中追踪

可以定义为周期性执行
周期性执行中做了预防措施,防止定义过小的周期,引起死循环,占用过高CPU

每次执行可追踪
每次执行,如果在debug模式下将记录执行时间,对执行异常也进行捕获打印日志,方便追踪bug

线程可停止
Java提供的线程并没有提供停止方法,该封装中通过一个标志位实现该功能,能够中断线程

结语:

线程虽然简单,但在实际使用中也要注意规范,每个线程都是随意new,随意使用的话会造成后续维护,bug追踪方便极大的困难。建议项目中的线程都遵从同一个标准。本文仅是个人实践拙见,欢迎拍砖!

最后送上我近期总结的面试资料图,各位有需要的可以加我Q群809389099获取!

架构技术进阶资料

最新面试真题


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值