多线程代码案例--实现定时器

简介: 多线程代码案例--实现定时器

hello,今天为大家带来定时器 的实现

定时器是用带有优先级的阻塞队列实现的(也就是带有阻塞功能的小根堆)


定时器是多线程中让线程更加高效的执行的手段,,就是时间到了,让该任务执行,在Java标准库中有自己的实现,Timer类,它的核心方法是schedule,下面来看看它的具体代码

1.标准库的实现

import java.util.TimerTask;
//定时器
import java.util.Timer;
public class ThreadDemo5 {
        public static void main(String[] args) {
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello4");
                }
            }, 4000);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello3");
                }
            }, 3000);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello2");
                }
            }, 2000);
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("hello1");
                }
            }, 1000);
            System.out.println("hello0");
        }
    }

我在学习这部分的时候,我产生了一个巨大的疑问困扰了我好几天,我在想这个定时器到底有几几个线程,想了好久我想通了,其实这就是一个线程,而schedule方法中写的是线程要执行的任务

一个线程可以执行多个任务,而Timer类内置了一个前台线程


因此,是只有一个线程和多个任务

下面,我们来进行定时器的实现


定时器就保证了多个线程有序的执行,那么有细心的老铁就发现了,这个运行一直没有结束,是为啥呢?

因为Timer类内置了一个线程,这个线程是前台线程,我们知道前台线程决定了线程是否结束,而前台线程会阻止线程的结束,所以代码会一直运行


重点来了,这个标准库自带的版本很简单,我们要自己咋样实现一个定时器呢?

需要一个带优先级的阻塞队列

2.自己实现定时器


import java.util.concurrent.PriorityBlockingQueue;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: WHY
 * Date: 2023-03-23
 * Time: 15:28
 */
//自己实现一个定时器
//表示一个执行的任务
class MyTask implements Comparable<MyTask>{//实现堆就要写比较规则,根据啥比较的
    public Runnable runnable;//这里是runnable类型的是因为根据源码写的
    public long time;//任务执行的绝对时间
    public MyTask(Runnable runnable,long delay){
        this.runnable=runnable;
        this.time=System.currentTimeMillis()+delay;//绝对时间戳=(当前时间-基准时间)+任务多久后执行的时间
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time-o.time);
    }
}
class MyTimer{
    //创建带有阻塞功能的优先级阻塞队列
   private Object locker=new Object();
    private PriorityBlockingQueue<MyTask> queue=new PriorityBlockingQueue<>();
   public void schedule(Runnable runnable,long delay){
       //根据参数,构造任务,插入队列中
       MyTask myTask=new MyTask(runnable,delay);
       queue.put(myTask);
        synchronized (locker){
           locker.notify();
       }
   }
   //构造线程,执行具体任务
    public MyTimer(){
       Thread t=new Thread(()->{
           while(true){
               try {
                   synchronized (locker) {
                   MyTask myTask= queue.take();
                   long curTime=System.currentTimeMillis();
                   if (myTask.time <= curTime) {
                            //时间到了,执行该任务
                       myTask.runnable.run();
                   }else{
                       //时间还没到,所以需要将拿出的队列放回去
                //put和take方法带有阻塞功能,peek没有,所以不用
                       queue.put(myTask);
                           locker.wait(myTask.time-curTime);
                       }
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
       });
       t.start();
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        MyTimer myTimer=new MyTimer();
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 4");
            }
        },4000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 3");
            }
        },3000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 2");
            }
        },2000);
        myTimer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello 1");
            }
        },1000);
    }
}

eb7d1509469b4fb3ba956d31b666eb9f.png


经过验证,代码很好的执行了多个任务

写这个代码要注意:


1.堆的实现一定要写比较规则

2.会出现忙等现象(解决办法加wait和notify)

这个代码中的wait和notify的作用是啥呢

当目前队首元素不满足条件,就重新塞回去,然后阻塞等待,为啥呢,因为当在schedule方法中创建出新任务时如果这个任务时间符合条件,那就进入线程执行,那么就在创建新的任务的时候唤醒wait,让线程重新进入循环(重点理解)!!!

为啥加锁一定要写到整个try那里,不能写到wait那里吗,我们来分析一下


我们都知道,线程的调度是随机的. 假设t1在执行的过程中,执行到queue.put()方法即将执行wait时,t2开始执行,现在来了一个时间为14:10的任务,然后插入了队列中,然后进行notify,这个时候的notify相当于空打一炮,此时都还没有wait,notify就唤醒了个寂寞,然后现在t1执行wait,注意,在执行wait时就已经解锁并且阻塞等待了,此时进行相减的是14:30-14:30,那么14:10分的任务就被错过了,所以这就是一直等,错过了,那么如果写成对整个try语句加锁,就不一样了


也就是说假设任务时间是14:30,目前时间是14:00,那么现在取出这个任务,判断大小,发现不符合,所以要放回去,然后现在把14:30放回去了,即将wait的时候,t2开始执行,取出14:10分的任务,进行唤醒操作,但是现在t1还没有执行到t1的wait,所以唤醒就没有用了.那么任务时间就还是14:30,没有被更新为14:10,t1继续执行wait,此时任务时间还是14:30,所以错过过14:10分的任务,也就是t2更新的任务t1没有接收到


d04a4e4dab99485b81d8913560a42b46.png


现在这一段操作都是原子的,t2想执行也要等到t1释放锁,所以是线程安全的

这个代码比较复杂,具体注释写在代码旁边了,需要的老铁可以看一看

今天的讲解就到这里,我们下期再见

相关文章
|
3月前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
58 3
|
8天前
|
安全 Java 调度
python3多线程实战(python3经典编程案例)
该文章提供了Python3中多线程的应用实例,展示了如何利用Python的threading模块来创建和管理线程,以实现并发执行任务。
12 0
|
2月前
|
Java Windows
【Azure Developer】Windows中通过pslist命令查看到Java进程和线程信息,但为什么和代码中打印出来的进程号不一致呢?
【Azure Developer】Windows中通过pslist命令查看到Java进程和线程信息,但为什么和代码中打印出来的进程号不一致呢?
|
2月前
|
消息中间件 安全 Kafka
"深入实践Kafka多线程Consumer:案例分析、实现方式、优缺点及高效数据处理策略"
【8月更文挑战第10天】Apache Kafka是一款高性能的分布式流处理平台,以高吞吐量和可扩展性著称。为提升数据处理效率,常采用多线程消费Kafka数据。本文通过电商订单系统的案例,探讨了多线程Consumer的实现方法及其利弊,并提供示例代码。案例展示了如何通过并行处理加快订单数据的处理速度,确保数据正确性和顺序性的同时最大化资源利用。多线程Consumer有两种主要模式:每线程一个实例和单实例多worker线程。前者简单易行但资源消耗较大;后者虽能解耦消息获取与处理,却增加了系统复杂度。通过合理设计,多线程Consumer能够有效支持高并发数据处理需求。
85 4
|
2月前
|
数据采集 Java Python
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
|
2月前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
33 0
|
4月前
|
Java
【代码诗人】Java线程的生与死:一首关于生命周期的赞歌!
【6月更文挑战第19天】Java线程生命周期,如诗般描绘了从新建到死亡的旅程:创建后待命,`start()`使其就绪,获得CPU则运行,等待资源则阻塞,任务完或中断即死亡。理解生命周期,善用锁、线程池,优雅处理异常,确保程序高效稳定。线程管理,既是艺术,也是技术。
26 3
|
4月前
|
Java
【代码诗人】Java线程的生与死:一首关于生命周期的赞歌!
【6月更文挑战第19天】在Java中,线程经历新建、就绪、运行、阻塞和死亡5个阶段。通过`start()`从新建转为就绪,进而可能运行;阻塞可能因等待资源;完成任务或中断后死亡。管理线程生命周期涉及合理使用锁、线程池、异常处理和优雅关闭,如使用`volatile`和中断标志。了解这些,能提升程序效率和稳定性。
25 2
|
4月前
|
安全 Java 容器
线程池,定时器以及阻塞队列(生产者/消费者模型)
线程池,定时器以及阻塞队列(生产者/消费者模型)
26 0
|
2月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
64 1