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

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);

    }


}

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

 

写这个代码要注意:

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没有接收到

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

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

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

猜你喜欢

转载自blog.csdn.net/weixin_61436104/article/details/129733379