很多时候,我们需要定时任务实现一些诸如刷新,心跳,保活等功能。这些定时任务往往逻辑很简单,使用定时任务的框架(例如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. 一步一图带你深入理解 Linux 物理内存管理

    1. 前文回顾 在上篇文章 <深入理解 Linux 虚拟内存管理> 中,笔者分别从进程用户态和内核态的角度详细深入地为大家介绍了 Linux 内核如何对进程虚拟内存空间进行布局以及管理的相 ...

  2. c#winfrom通讯录管理系统

    一个简单的通讯录管理系统,适合毕业设计. 主要实现以下功能 1.系统登录 2.增加联系人 3.修改和删除联系人 4.查找联系人 5.系统用户管理 首先先搭建数据库. 我这边使用的版本是sqlserve ...

  3. c# 使用委托子窗体改变父窗体控件

    首先创建两个窗体,在窗体1和窗体2放上对应的控件 在窗体1的代码如下 using System; using System.Collections.Generic; using System.Comp ...

  4. 快捷打开cmd管理员模式

    win+s-搜索cmd 直接回车:普通用户模式的cmd CTRL+SHIFT+回车:管理员模式的cmd

  5. 关于在linux上vm virtualbox读取不到U盘问题的解决

    1.设置usb2.0模式 如果你没安装拓展插件的话,调成usb2.0就会出现无效的配置这个提示,并且启动虚拟机会报 Implementation of the USB 2.0 controller n ...

  6. 【大数据面试】Hbase:数据、模型结构、操作、读写数据流程、集成、优化

    一.概述 1.概念 分布式.可扩展.海量数据存储的NoSQL数据库 2.模型结构 (1)逻辑结构 store相当于某张表中的某个列族 (2)存储结构 (3)模型介绍 Name Space:相当于数据库 ...

  7. 简单的Dos命令学习

    Dos命令学习 打开CMD的方式 从菜单栏打开,windows系统里 win+R 输入cmd 在任意文件夹下,按住shift+右键,点击打开PowerShell 在资源管理器的地址栏前面加上cmd+空 ...

  8. Redis分布式锁应用

    Redis锁的使用 起因:分布式环境下需对并发进行逻辑一致性控制 架构:springboot2.Redis IDEA实操 先新建RedisLock组件 注:释放锁使用lua脚本保持原子性 @Compo ...

  9. JavaScript入门⑦-DOM操作大全

    JavaScript入门系列目录 JavaScript入门①-基础知识筑基 JavaScript入门②-函数(1)基础{浅出} JavaScript入门③-函数(2)原理{深入}执行上下文 JavaS ...

  10. 如何用3D流体实现逼真水流效果?

    华为应用市场在2022年HDC大会期间发布了一款3D水流主题,基于华为HMS Core Scene Kit服务能力,展现立体灵动的水流岛屿,可跟随用户指尖实现实时流体波动效果,既趣味又解压. 让变幻莫 ...