使用java.util.Timer实现定时任务,详解Thread.sleep() in a loop, probably busy-waiting问题
很多时候,我们需要定时任务实现一些诸如刷新,心跳,保活等功能。这些定时任务往往逻辑很简单,使用定时任务的框架(例如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问题的更多相关文章
- java.util.logging.Logger使用详解 (转)
http://lavasoft.blog.51cto.com/62575/184492/ ************************************************* java. ...
- java.util.ResourceBundle国际化用法详解
java.util.ResourceBundle国际化用法详解 初识国际化和ResourceBundle 这个类主要用来解决国际化和本地化问题.国际化和本地化可不是两个概念,两者都是一起出现的.可以说 ...
- java.util.logging.Logger 使用详解
概述: 第1部分 创建Logger对象 第2部分 日志级别 第3部分 Handler 第4部分 Formatter 第5部分 自定义 第6部分 Logger的层次关系 参考 第1部分 创建Logger ...
- java.util.concurrent.atomic 包详解
Atomic包的作用: 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心: Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作 关于CAS compar ...
- java.util.logging.Logger使用详解
一.创建Logger对象 static Logger getLogger(String name) 为指定子系统查找或创建一个 logger. static Logger ge ...
- java.util.ConcurrentModificationException 异常问题详解
环境:JDK 1.8.0_111 在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常, ...
- 2.java.util.logging.Logger使用详解
一.java.util.logging.Logger简介 java.util.logging.Logger不是什么新鲜东西了,1.4就有了,可是因为log4j的存在,这个logger一直沉默着, 其实 ...
- java.util.concuttent Callable Future详解
在传统的多线程实现方式中(继承Thread和实现Runnable)无法直接获取线程执行的返回结果,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦. 从 ...
- Java定时器Timer使用方法详解
感谢大佬:https://www.jb51.net/article/129808.htm 一.概念 定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和 ...
- Java定时器Timer的使用详解
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6374714.html 定时器在Web开发中使用得不是很多.这里主要列举一下使用定时器的步骤,方便日后使用时查 ...
随机推荐
- uboot引导应用程序
uboot默认是支持执行应用程序的,就像引导内核一样,我们也可以自己写一个应用程序,让uboot启动时引导. 在uboot examples/standalone 目录下,有hello_world.c ...
- Redis如何模糊匹配Key值
Redis模糊匹配Key值 使用Redis的scan代替Keys指令: public Set<String> scan(String matchKey) { Set<String&g ...
- Django框架F查询与Q查询(全面了解)
一:F与Q查询 1.F查询的作用 能够帮助你直接获取到列表中某个字段对应的数据 注意: 在操作字符串类型的数据的时候, F不能够直接做到字符串的拼接 2.查询卖出书大于库存数的书籍 # 导入F查询 f ...
- flask博客项目之tinymce图片上传
查看当前的博客发表情况 截图一张立马粘贴进来 点击发表,显示数据太长 不断撤退回到刚刚页面 删除大图,换成小图,上传方式 点击发表可以成功发表 数据库中查看,是把图片生成这种编码后字符串方式存储的了, ...
- Spring之后置处理器
Spring的后置处理器是Spring对外开发的重要扩展点,允许我们接入Bean的实例化流程中,以达到动态注册BeanDefinition.动态修改BeanDefinition.动态修改Bean的 ...
- 字符编码:Unicode & UTF-16 & UTF-8
ASCII码 使用一个字节(8位),对128个字符进行编码: 最高位始终为0: 码数范围为0000_0000(0x00)到0111_1111(0x7F): Unicode 开始的编码设计 使用两个字节 ...
- [图像处理] YUV图像处理入门3
5 yuv420格式的灰阶测试图 本程序中的函数主要是为YUV420P视频数据流的第一帧图像添加边框.函数的代码如下所示: /** * @file 5 yuv_graybar.cpp * @autho ...
- OpenMP 原子指令设计与实现
OpenMP 原子指令设计与实现 前言 在本篇文章当中主要与大家分享一下 openmp 当中的原子指令 atomic,分析 #pragma omp atomic 在背后究竟做了什么,编译器是如何处理这 ...
- 使用IIS配置代理,转发POST和GET访问,配置IIS接口转发失效问题处理
先说一下可能引发配置失败的原因:大概率是你的Application Request Routing没有配置好,或者你的正则表达没有搞好,往下看步骤自己对照哇~ 1.确保服务器已经安装IIS 2.下载U ...
- vue小技巧-vue引入样式、修改难以修改的子组件内部标签样式