[JavaEE] timer


Column introduction: JavaEE from entry to advanced

Topic source: Leetcode, Niuke, Jianzhi offer .

Creation goal: record the learning process of learning JavaEE

I hope to help others while improving myself, and make progress together with everyone and grow up with each other.

Academic qualifications represent the past, ability represents the present, and learning ability represents the future! 


Table of contents: 

1. The concept of timer

2. Timers in the standard library

3. Realize the timer

3.1 Composition of a fixed period:

3.2 Implementation steps:


1. The concept of timer

A timer is similar to an "alarm clock" and is an important component in software development. A certain piece of code will be executed after a set time is reached.

For example, in network communication, if the other party does not return data within 500ms, then execute the task in the timer: disconnect and reconnect.

For example, implement a Map, and hope that the key in the Map will expire (automatically delete) after 3s.

...............................

Scenarios similar to the above require the use of timers.


2. Timers in the standard library

  • A Timer class is provided in the standard library, and the core method of the Timer class is schedule().
  • The schedule () method contains two parameters, the first parameter specifies the task code to be executed, and the second parameter specifies how long it will take to execute (in ms)

public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },500);
    }

3. Realize the timer

3.1 Composition of a fixed period:

  • A priority blocking queue is used to store pending tasks.

Why is there a priority? If the priority queue is sorted by the waiting time (delay), each time you only need to take out the element at the head of the queue, you can efficiently find out the task with the smallest delay.

  • Each element in the queue is a Task object.
  • Task has a time attribute, and the first element of the queue is the element to be executed.
  • At the same time, there is a scanning thread that keeps scanning the first element of the queue to check whether the first element of the queue has reached the execution time.

3.2 Implementation steps:

  • 1. The core method provided by the Timer class is schedule(), which is used to register a task and specify when the task will be executed.
class MyTimer{
    public void schedule(Runnable runnable, long after){
        //具体执行的任务
    }
}
  • 2. The Task class is used to describe a task, which contains a Runnable object and a time millisecond timestamp. The object of this class needs to be placed in the priority queue, so it needs to implement the Comparable interface.
class MyTask implements Comparable<MyTask>{
    private Runnable runnable;
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime(){
        return time;
    }
    //执行任务
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTask1 o) {
        return (int) (this.time-o.time);
    }
}
  • 3. In the Timer instance, organize several Task objects through PriorityQueue, and insert Task objects into the queue through schedule.
class MyTimer{
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable , long after){
        MyTask1 myTask = new MyTask(runnable , System.currentTimeMillis()+after);
        queue.put(myTask);
    }
}
  • 4. There is a scanning thread in the Timer class, which constantly checks whether the first element of the queue has reached the execution time.
class MyTimer1 {
    Thread scan = null;
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
    }

    public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                try {
                    MyTask1 myTask1 = queue.take();
                    if (System.currentTimeMillis() < myTask1.getTime()) {
                        //时间还没到把任务塞回去
                        queue.put(myTask1);
                    } else {
                        //时间到了执行任务
                        myTask1.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        scan.start();
    }
}

But there is a very serious problem in the current code, that is, the while(true) loop is too fast, causing meaningless CPU waste.

  • 5. Use wait/notify to solve the while(true) problem, and modify the schedule method of MyTimer, once a new task is added, notify wait to judge again.
public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
        //一但加入新的元素就唤醒 wait ,重新判断
        synchronized (this) {
            this.notify();
        }
    }
public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                try {
                    MyTask1 myTask1 = queue.take();
                    if (System.currentTimeMillis() < myTask1.getTime()) {
                        //时间还没到把任务塞回去
                        queue.put(myTask1);
                        synchronized (this) {
                            this.wait(myTask1.getTime()-System.currentTimeMillis());
                        }
                    } else {
                        //时间到了执行任务
                        myTask1.run();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        scan.start();
    }

At this time, there is still a little flaw in the code, assuming that the current time is 13:00, if the execution time of the task at the head of the queue is 14:00, then when the code executes to queue.put(myTask1) ; when the thread is dispatched by the CPU, and this At the same time, another thread calls the schedule method, registers a task to be executed at 13:30, puts it in the head of the queue and notifies wait. But at this time, the notify method is empty, and when the scanning thread is scheduled back, wait still has to wait for 1h , this chance causes the task at 13:30 to miss its execution time.

The root cause of the above problem is that the operations of take() and wait are not atomic. If a lock is added between take() and wait to ensure that no new tasks will come in during the execution, the problem will be solved naturally.

public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                synchronized (this) {
                    try {
                        MyTask1 myTask1 = queue.take();
                        if (System.currentTimeMillis() < myTask1.getTime()) {
                            //时间还没到把任务塞回去
                            queue.put(myTask1);
                            this.wait(myTask1.getTime()-System.currentTimeMillis());
                        } else {
                            //时间到了执行任务
                            myTask1.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        scan.start();
    }

The complete code is as follows:

class MyTask1 implements Comparable<MyTask1>{
    private Runnable runnable;
    private long time;

    public MyTask1(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }
    //获取当前任务的时间
    public long getTime(){
        return time;
    }
    //执行任务
    public void run(){
        runnable.run();
    }

    @Override
    public int compareTo(MyTask1 o) {
        return (int) (this.time-o.time);
    }
}
/**
 * 定时器类
 */
class MyTimer1 {
    Thread scan = null;
    PriorityBlockingQueue<MyTask1> queue = new PriorityBlockingQueue<>();

    public void schedule(Runnable runnable, long after) {
        MyTask1 myTask1 = new MyTask1(runnable, System.currentTimeMillis() + after);
        queue.put(myTask1);
        //一但加入新的元素就唤醒 wait ,重新判断
        synchronized (this) {
            this.notify();
        }
    }

    public MyTimer1() {
        scan = new Thread(() -> {
            while (true) {
                synchronized (this) {
                    try {
                        MyTask1 myTask1 = queue.take();
                        if (System.currentTimeMillis() < myTask1.getTime()) {
                            //时间还没到把任务塞回去
                            queue.put(myTask1);
                                this.wait(myTask1.getTime()-System.currentTimeMillis());
                        } else {
                            //时间到了执行任务
                            myTask1.run();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        scan.start();
    }
}
public class ThreadDemo8 {
    public static void main(String[] args) {
        MyTimer1 myTimer1 = new MyTimer1();
        myTimer1.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,1");
            }
        },300);
        myTimer1.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,2");
            }
        },600);
    }
}

Guess you like

Origin blog.csdn.net/liu_xuixui/article/details/128576133