并发编程之定时任务&定时线程池
定时线程池类的类结构图
用来处理定时任务或延时任务
它接收SchduledFutureTask类型的任务,是线程池调度任务的最小单位,有三种提交任务的方式:
- schedule
- scheduledAtFixedRate
- scheduledWithFixedDelay
延迟执行任务,或者先提交线程执行任务,得到返回值再执行下面的。异步阻塞,等上面的结果
scheduleAtFixedRate解析
看段代码
@Slf4j
public class ScheduThreadPool {
static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor=
new ScheduledThreadPoolExecutor(1);
public static void main(String[] args) {
scheduledThreadPoolExecutor.scheduleAtFixedRate(()->{
log.info("send heart beat");
},1000,2000, TimeUnit.MILLISECONDS);
}
}
运行结果:
延迟1秒,每2秒执行一次。我们继续看
@Slf4j
public class ScheduThreadPool {
static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor=
new ScheduledThreadPoolExecutor(1);
public static void main(String[] args) {
scheduledThreadPoolExecutor.scheduleAtFixedRate(()->{
log.info("send heart beat");
Long startTime=System.currentTimeMillis(),nowtime=startTime;
while ((nowtime-startTime)<5000){
nowtime=System.currentTimeMillis();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("task over");
},1000,2000, TimeUnit.MILLISECONDS);
}
}
运行结果:
2秒的周期,执行任务的时间需要5秒。5秒执行完会立刻执行下一个任务。避免产生大量的堆积
scheduleWithFixedDelay
@Slf4j
public class ScheduThreadPool {
static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor=
new ScheduledThreadPoolExecutor(1);
public static void main(String[] args) {
scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{
log.info("send heart beat");
Long startTime=System.currentTimeMillis(),nowtime=startTime;
while ((nowtime-startTime)<5000){
nowtime=System.currentTimeMillis();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("task over");
},1000,2000, TimeUnit.MILLISECONDS);
}
}
运行结果:
任务5秒钟执行完,还需再等2秒再执行下一个任务。相当于7秒执行1次。
如果出现异常
@Slf4j
public class ScheduThreadPool {
static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor
=new ScheduledThreadPoolExecutor(1);
public static void main(String[] args) {
scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{
log.info("send heart beat");
Long startTime=System.currentTimeMillis(),nowtime=startTime;
while ((nowtime-startTime)<5000){
nowtime=System.currentTimeMillis();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("task over");
throw new RuntimeException("unexpect error stop");
},1000,2000, TimeUnit.MILLISECONDS);
}
}
则执行完一次就不会继续了。任务已经丢了,线程没有可执行的任务。
延迟类的任务一定要做异常的捕获。强行抛异常任务会丢失。
timer
要定期执行任务jdk里有没有工具类,有timer类,但是不推荐用,举个例子
Timer time=new Timer();
time.scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
log.info("heart start");
throw new RuntimeException("unexpect error stop");
}
},1000,2000);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
time.scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
log.info("heart start");
throw new RuntimeException("unexpect error stop");
}
},1000,2000);
Timer是单线程任务。
private final TimerThread thread = new TimerThread(queue);
线程都没了自然不会执行第2个timer任务。很容易导致程序挂掉,这也是阿里规范里明确要求不能用的。
对比一下线程池模式
scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{
log.info("send heart beat");
Long startTime=System.currentTimeMillis(),nowtime=startTime;
while ((nowtime-startTime)<5000){
nowtime=System.currentTimeMillis();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("task over");
throw new RuntimeException("unexpect error stop");
},1000,2000, TimeUnit.MILLISECONDS);
scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{
log.info("send heart beat");
Long startTime=System.currentTimeMillis(),nowtime=startTime;
while ((nowtime-startTime)<5000){
nowtime=System.currentTimeMillis();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("task over");
},1000,2000, TimeUnit.MILLISECONDS);
运行结果
可以看到第2个线程的定时不会受到影响。
使用场景
分布式锁-redis实现
先解释下setnx
只在键 key
不存在的情况下, 将键 key
的值设置为 value
。
若键 key
已经存在, 则 SETNX
命令不做任何动作。
SETNX
是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值
命令在设置成功时返回 1 , 设置失败时返回 0 。
代码示例
redis> EXISTS job # job 不存在
(integer) 0
redis> SETNX job "programmer" # job 设置成功
(integer) 1
redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败
(integer) 0
redis> GET job # 没有被覆盖
"programmer"
回归正题
redis做分布式锁性能更好
if(setnx("",锁的过期时间){ //拿到分布式锁
//执行逻辑,查db,进行排序等等
}else{
park,sleep
}
会出问题这段代码,如果app拿到锁挂了,我们一般设置过期时间。假设锁的过期时间是5秒钟,如果在5秒钟的时间内没执行完,锁又失效了,其他app可以拿到锁。换句话说锁的过期时间是不确定的。
可以采用定时线程池,假设5秒钟过期。每隔2秒钟去定时续约(续命)一次。看一下redis锁在不在,如果还在则延迟,直到逻辑执行完释放。因为不确定业务逻辑执行多少时间。
注册中心Eureka
微服务服务与注册发现中心Eureka
假设电商项目,存在订单服务,库存服务,消息服务,产品管理服务。通常下订单需要调好几个服务。
看下图