多线程案例之定时器

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、定时器

代码中的定时器,通常都是设定多长时间之后执行某个动作
应用:客户端,服务器开发,客户端设置一个超时时间,等待服务器相应

1.标准库中的定时器

schedule方法(安排任务)参数有俩个

import java.util.Timer;
timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到,快起床");
            }
        },3000);

在这里插入图片描述

类似于runnable,要安排的任务是啥,就是一个runnable,咱们要做的是继承timerTask,然后重写run方法, 从而在指定要执行的任务
delat经过多长时间执行

一个定时器可以同时安排多个任务,这里的线程是前台线程,进程不会退出

代码演示

import java.util.Timer;
import java.util.TimerTask;
//演示标准库中的定时器
public class demo24 {
    public static void main(String[] args) {
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到,快起床");
            }
        },3000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到2!");
            }
        },4000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("时间到3!");
            }
        },5000);
        System.out.println("开始计时");
    }
}

2.自己实现的定时器

schedule第一个参数是任务,需要能够描述这个任务,任务包含俩个信息,一个是执行啥工作,一个是啥时候执行

在这里插入图片描述

用mytimer来管理多个任务,timer可以安排多个任务
很明显,设置的时间越短的任务,越要先执行,如果使用arraylist,元素无序,不方便找到时间最小的任务 考虑优先阻塞队列来实现

 private BlockingQueue<Mytask> queue = new PriorityBlockingQueue<>();

schdule可能在多线程中调用,使用blockingqueue实现

任务已经安排在优先级阻塞队列当中,接下来就需要从队列中取元素了,创建一个单独的扫描线程,让这个线程不停的检查队首元素,时间是否到了,如果时间到了就执行任务
在这里插入图片描述

由于阻塞队列,无法阻塞取对首元素 因此就需要先取出任务,然后才能够判断任务时间是否到了,如果任务时间没到,还得把任务塞回去。

4.运行时出现异常:
在这里插入图片描述
需要自定义一个comparable方法来选择比较方式

class Mytask implements Comparable<Mytask>{
  @Override
    public int compareTo(Mytask o) {
        return (int) (this.time - o.time);
    }
    }

错误代码

5.此时定时器还有一个严重的问题:

在这里插入图片描述
纯纯的浪费cpu资源,一直在忙等,不断的询问时间,进行判断比较,cpu并没有被空闲出来

错误解决方案1:
在这里插入图片描述
在这里插入图片描述
给等待的过程加锁,当有任务安排时,尝试唤醒wait,重新分配任务,但是
当put线程竞争到锁,安排一个更早的任务,schedule执行完毕,执行notify,回到扫描线程
由于take操作不是原子的,导致没进行到wait时候,curtime是旧任务时间,导致空打一炮(虽然通知了,但没有唤醒任何人),没有及时唤醒wait,发现当前任务时间依然是旧的时间,错过了任务

错误写法二:
在这里插入图片描述
错误原因:会造成死锁,扫描线程取队首元素时候,发现队列为空,阻塞等待(需要另一个线程安排任务才能唤醒),由于扫描线程上了锁,此时schdule线程无法拿到锁,就会一直阻塞等待

正确代码:

正确做法(锁的粒度加大)
在这里插入图片描述

刚才出现问题的原因,就是notify在take和wait之间执行的,现在把扫描线程中的锁范围放大了,可以避免notify在take和wait之间执行了,扫描线程会先拿到锁,然后take,中间逻辑,一直到wait,在这个过程中,schedule会阻塞等待,直到扫描线程执行了wait,扫描线程释放了锁,schdule拿到锁,进行通知,这个时候wait被唤醒,下次重新取队首元素,就会把1:00执行的任务取出来了

代码演示:

package Threading;

import java.util.ArrayDeque;
import java.util.PriorityQueue;
import java.util.TimerTask;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

//自己实现一个定时器
class Mytask implements Comparable<Mytask> {
    //要执行的任务
    private Runnable runnable;
    //什么时间来执行任务(是一个时间戳)
    private long time;

    public Mytask(Runnable runnable, long delay) {
        this.runnable = runnable;
        this.time = time;
    }

    public Runnable getRunnable() {
        return runnable;
    }

    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(Mytask o) {
        return (int) (this.time - o.time);
    }

}

class MyTimer {
    private BlockingQueue<Mytask> queue = new PriorityBlockingQueue<>();
    private Object locker = new Object();

    public MyTimer() {
        //扫描一个线程
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    synchronized (locker) {
                        //取出队首元素
                        Mytask task = queue.take();
                        //假设当前时间是2:30,任务设定时间是2:30,显示就要执行任务了
                        //假设当前时间是2:30,任务设定时间是2:29,也是到点了,也要执行任务
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            //到点了,该执行任务了
                            task.getRunnable().run();
                        } else {
                            //还没到点
                            queue.put(task);
                            locker.wait(task.getTime() - curTime);
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }

    public void schedule(Runnable runnable, long after) throws InterruptedException {
        Mytask mytask = new Mytask(runnable, after);
        queue.put(mytask);
        synchronized (locker) {
            locker.notify();
        }
    }
}

public class demo25 {
    public static void main(String[] args) throws InterruptedException {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间到1!");
            }
        }, 3000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间到2!");
            }
        }, 4000);
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("时间到3!");
            }
        }, 5000);
        System.out.println("开始计时");
//        ArrayDeque<String> a=new ArrayDeque<>();
//        a.peekLast();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值