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

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

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释放锁,所以是线程安全的

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

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

相关文章
|
1月前
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
120 12
|
2月前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
76 3
|
3月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
54 6
|
4月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
44 1
|
4月前
|
安全 Java
【多线程-从零开始-拾】Timer-定时器
【多线程-从零开始-拾】Timer-定时器
52 0
|
5月前
|
安全 Java 调度
python3多线程实战(python3经典编程案例)
该文章提供了Python3中多线程的应用实例,展示了如何利用Python的threading模块来创建和管理线程,以实现并发执行任务。
111 0
|
6月前
|
开发者 C# 存储
WPF开发者必读:资源字典应用秘籍,轻松实现样式与模板共享,让你的WPF应用更上一层楼!
【8月更文挑战第31天】在WPF开发中,资源字典是一种强大的工具,用于共享样式、模板、图像等资源,提高了应用的可维护性和可扩展性。本文介绍了资源字典的基础知识、创建方法及最佳实践,并通过示例展示了如何在项目中有效利用资源字典,实现资源的重用和动态绑定。
175 0
|
6月前
|
数据采集 Java Python
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
|
6月前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
60 0
|
8天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
38 17