很多时候,我们需要定时任务实现一些诸如刷新,心跳,保活等功能。这些定时任务往往逻辑很简单,使用定时任务的框架(例如springboot @Scheduled)往往大材小用。

下面是一个定时任务的典型写法,每隔30s发送心跳

    public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
try {
//发送心跳的业务代码
heartbeat();
Thread.sleep(1000L * 30);
} catch (Exception e) {
//print the error log
e.printStackTrace();
}
}
});
t.start();
}

如果你使用了IDEA或者其他的Java集成开发环境,你会发现编辑器会提示你Call to 'Thread.sleep()' in a loop, probably busy-waiting 点开提示信息,发现这样的写法有可能会导致忙等待死锁

忙等待 busy-waiting占用大量cpu资源,cpu利用率会达到99%,可能会完全吃掉一核cpu资源,导致其他业务甚至是宿主机的异常。

你可能会说,这样的写法怎么会导致忙等待 busy-waiting呢,我明明已经sleep()了呀,心跳任务每隔30s才执行一次啊。

如果heartbeat()抛出了异常(空指针,代码错误,网络错误等),sleep()语句就会跳过,进入了异常分支,休眠30s的目的无法达到,程序就会进入死循环,以疯狂的速度执行heartbeat()语句。更有甚者,如果你捕获异常并打印日志,日志甚至能很快写满整个硬盘。

当然,你也可以将sleep语句提到前面来,先执行Thread.sleep(),这样,可以规避忙等待风险。但是还有另外一个坑在等待着你。点开Thread.sleep()文档

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.

重点在这一句The thread does not lose ownership of any monitors. monitor就是Java的重量级锁,平时我们使用的synchronized关键字就是基于monitor实现的。也就是说,线程在sleep的过程中并不会释放所持有的锁,这会导致严重的并发问题,甚至是死锁。你可能又会说,我写的代码里没有锁没有synchronized关键字,我能不能放心使用呢?

答案是不能,你的代码里没锁,不代表你依赖的代码里没锁,不代表后续的维护者不会加锁。这是一个技术债务,在绝大多数的情况下都不会出问题,但也许有一天会暴雷。

作为一个负责人的开发者,作为一个有着代码洁癖的人,作为一个无法忍受IDEA黄色提示的人,作为一个简洁至上的人,作为一个不想滥用框架的人,我推荐使用jdk自带的java.util.Timer代码如下

    public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
//发送心跳的业务代码
heartbeat();
} catch (Exception e) {
//print the error log
e.printStackTrace();
} }
}, 1000L * 30, 1000L * 30);
}

同样实现的是30s间隔执行心跳操作,使用Timer不会有上述我们说的忙等待死锁的风险。Timer内部使用了一个线程,和我们单独new Thread()的效果是一样的。值得一提的是,Timer是基于wait(),notify()机制实现的,与sleep()相比,wait()会释放锁(也就是monitor)。

使用java.util.Timer实现定时任务,详解Thread.sleep() in a loop, probably busy-waiting问题的更多相关文章

  1. java.util.logging.Logger使用详解 (转)

    http://lavasoft.blog.51cto.com/62575/184492/ ************************************************* java. ...

  2. java.util.ResourceBundle国际化用法详解

    java.util.ResourceBundle国际化用法详解 初识国际化和ResourceBundle 这个类主要用来解决国际化和本地化问题.国际化和本地化可不是两个概念,两者都是一起出现的.可以说 ...

  3. java.util.logging.Logger 使用详解

    概述: 第1部分 创建Logger对象 第2部分 日志级别 第3部分 Handler 第4部分 Formatter 第5部分 自定义 第6部分 Logger的层次关系 参考 第1部分 创建Logger ...

  4. java.util.concurrent.atomic 包详解

    Atomic包的作用: 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心: Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作 关于CAS compar ...

  5. java.util.logging.Logger使用详解

    一.创建Logger对象   static Logger getLogger(String name)           为指定子系统查找或创建一个 logger. static Logger ge ...

  6. java.util.ConcurrentModificationException 异常问题详解

    环境:JDK 1.8.0_111 在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常, ...

  7. 2.java.util.logging.Logger使用详解

    一.java.util.logging.Logger简介 java.util.logging.Logger不是什么新鲜东西了,1.4就有了,可是因为log4j的存在,这个logger一直沉默着, 其实 ...

  8. java.util.concuttent Callable Future详解

    在传统的多线程实现方式中(继承Thread和实现Runnable)无法直接获取线程执行的返回结果,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦. 从 ...

  9. Java定时器Timer使用方法详解

    感谢大佬:https://www.jb51.net/article/129808.htm 一.概念 定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和 ...

  10. Java定时器Timer的使用详解

     转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6374714.html 定时器在Web开发中使用得不是很多.这里主要列举一下使用定时器的步骤,方便日后使用时查 ...

随机推荐

  1. VsCode搭建一个React项目

    安装Node.js 使用 npm -v检查安装成功 目前的 node 中都会自带 npm 所以不需要重新下载 直接切换至淘宝镜像即可 1.临时使用 :npm --registry https://re ...

  2. os与sys模块,json模块

    一.os模块(重要) os模块主要与操作系统打交道 1.创建目录(文件夹) import os os.mkdir(r'a1') # 在执行文件所在的路径下创建单级目录a1 os.mkdir(r'a2\ ...

  3. STM32点亮LED的代码

    led.c #include "led.h" void LED_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2P ...

  4. 命令指定IP端口号

    tcping命令是针对tcp监控的,也可以看到ping值,即使源地址禁ping也可以通过tcping来监控服务器网络状态,除了简单的ping之外,tcping最大的一个特点就是可以指定端口. 将下载好 ...

  5. Python 为什么如此设计?

    大概两年半前,我萌生了要创作一个新的系列文章的想法,也就是"Python为什么",试图对 Python 的语法及特性提出"为什么"式的问题,以此加深对它的理解, ...

  6. Jmeter 之跨线程传参

    其他线程使用某个线程中提取的值,比如场景:客户端一直与服务端保持连接的同时进行其他业务操作 1.建立以下两个线程组,并添加相应业务接口 2.发送心跳时,需要token,在用户登录接口下添加提取器提取t ...

  7. JavaScript:函数:函数的参数

    声明函数的时候,有个括号,这里面可以加上函数的参数,这些参数,我们叫做形参(形式参数): 此时这些参数,也是已经声明了的变量,只是还没有赋值而已. 也可以不加,取决于函数的逻辑.如果函数需要从外部传进 ...

  8. JavaWeb项目编译前后的目录结构

    JavaWeb项目编译前后的目录结构 编译前 页面和视图都放在webapp目录下 编译后 webapps WEB-INF

  9. [深度学习] Pytorch模型转换为onnx模型笔记

    本文主要介绍将pytorch模型准确导出为可用的onnx模型.以方便OpenCV Dnn,NCNN,MNN,TensorRT等框架调用.所有代码见:Python-Study-Notes 文章目录 1 ...

  10. S2-016 CVE-2013-2251

    漏洞名称 S2-016(CVE-2013-2251) 通过操作前缀为"action:"/"redirect:"/"redirectAction:&qu ...