Heim  >  Artikel  >  Java  >  So implementieren Sie Anti-Shake und Throttling in Java

So implementieren Sie Anti-Shake und Throttling in Java

WBOY
WBOYnach vorne
2023-05-12 20:34:041151Durchsuche

Konzept

Debounce

Wenn ein Ereignis kontinuierlich ausgelöst wird und innerhalb eines bestimmten Zeitraums kein Ereignis erneut ausgelöst wird, wird die Ereignisverarbeitungsfunktion einmal ausgeführt. Wenn das Ereignis erneut ausgelöst wird, bevor die eingestellte Zeit eintrifft, wird die Verzögerung ausgelöst wird wieder von vorne beginnen.

  • Anti-Shake, das heißt, wenn in kurzer Zeit eine große Anzahl desselben Ereignisses ausgelöst wird, wird der Timer zurückgesetzt. Die Funktion wird erst dann ausgeführt, wenn das Ereignis nicht mehr ausgelöst wird wartet auf das angegebene Ereignis. Und dieser ganze Vorgang löst eine Like-Funktion beim Server aus. Prinzip: Stellen Sie einen Timer ein und lösen Sie die Ereignisverarbeitung nach einer bestimmten Zeit aus. Der Timer wird jedes Mal zurückgesetzt, wenn ein Ereignis ausgelöst wird.

  • Beispiel: Ein sehr einfaches Beispiel: Wenn Sie Ihren Freundeskreis verrückt mögen und dann die Likes abbrechen, wird durch diesen Vorgang der Timer gelöscht. Warten Sie, bis Sie das Klicken satt haben, und hören Sie 0,5 Sekunden lang auf zu klicken ausgelöst und geben Ihre Endergebnisse an den Server weiter.

  • Frage 1: Wenn das der Fall ist, wäre es dann nicht besser, die Anti-Shake-Funktion dem Frontend zu überlassen? Die Antwort ist ja, aber die Benutzererfahrung geht verloren. Ursprünglich mochten einige Benutzer es nur zum Spielen, aber jetzt werden Sie von Ihrem Frontend direkt darauf hingewiesen, dass der Vorgang zu schnell ist. Machen Sie bitte eine Pause. Ob Benutzer ihren Spaß verloren haben, dieser Punkt muss sich auch auf die Likes von QQ Space beziehen. Ich weiß zwar nicht, ob es Anti-Shake verwendet, aber es verfügt über animierte Likes und Stornierungen, so dass jedes Mal, wenn der Benutzer es betätigt, die Ausführung erfolgt Es wird eine Animation angezeigt, die das Benutzererlebnis erheblich verbessert.

  • Frage 2: Hier liegt also das Problem: Wenn Sie innerhalb einer bestimmten Zeitspanne weiterklicken, wird der Timer zurückgesetzt. Wenn er dann einen Tag und eine Nacht lang klickt, wird er dann mit der Ausführung aufhören? Theoretisch stimmt das, aber die Leute werden müde sein. Du kannst nicht weiter kämpfen, oder? Menschen können es also nicht, also können Maschinen und Skripte nur damit umgehen. Anti-Shake kann auch verwendet werden, um einige Skriptangriffe zu blockieren.

Throttle

Wenn ein Ereignis kontinuierlich ausgelöst wird, wird die Ereignisverarbeitungsfunktion garantiert nur einmal innerhalb eines bestimmten Zeitraums aufgerufen. Dies bedeutet, dass davon ausgegangen wird, dass ein Benutzer diese Funktion weiterhin auslöst und jeder Auslöser kürzer ist vorgegebenen Wert wird jedes Mal die Funktion Throttle aufgerufen.

Beim Gedanken daran denken viele Leute an eine Funktion, ja, sie dient dazu, wiederholte Einreichungen zu verhindern. Da diese Geschäftszeit jedoch schwer zu kontrollieren ist, wird empfohlen, sie für einige zustandslose Vorgänge zu verwenden. Zum Beispiel: Beim Aktualisieren der Rangliste scheint das Frontend ständig zu klicken. Um einen Absturz der Schnittstelle zu verhindern, führt das Backend tatsächlich nur alle 1 Sekunde eine echte Aktualisierung durch.

Der Unterschied

Anti-Shake besteht darin, dass mehrere Ausführungen in eine Ausführung umgewandelt werden, nachdem sie innerhalb einer bestimmten Zeit ausgelöst wurden.

Drosselung besteht darin, mehrere Ausführungen in eine bestimmte Zeit zu ändern, sie wird einmal ausgeführt, wenn die Zeit abgelaufen ist.

Java-Implementierung

Der Schlüssel zur Java-Implementierung von Anti-Shake und Drosselung ist die Timer-Klasse und die Runnable-Schnittstelle.

Unter diesen implementieren die Schlüsselmethoden cancel() im Timer Anti-Shake und scheme() die Drosselung. Nachfolgend finden Sie eine kurze Einführung in diese beiden Methoden.

Timer##cancel(): Nachdem Timer.cancel() aufgerufen wurde, wird der gesamte Timer-Thread beendet.

Das ist leicht zu verstehen. Solange Sie es innerhalb der angegebenen Zeit auslösen, werde ich es direkt abbrechen (), das heißt, es abbrechen und nicht ausführen lassen.

Timer##schedule(): Nachdem der Benutzer die Schedule()-Methode aufgerufen hat, muss er N Sekunden warten, bevor er die run()-Methode zum ersten Mal ausführt.

Dieses N ist die Zeit, die wir basierend auf dem Geschäft ausgewertet haben, und wird als Parameter übergeben.

Debounce

package com.example.test01.zhangch;

import java.util.Timer;
import java.util.TimerTask;

/**
 * @Author zhangch
 * @Description java 防抖
 * @Date 2022/8/4 18:18
 * @Version 1.0
 */
@SuppressWarnings("all")
public class DebounceTask {
    /**
     * 防抖实现关键类
     */
    private Timer timer;
    /**
     * 防抖时间:根据业务评估
     */
    private Long delay;
    /**
     * 开启线程执行任务
     */
    private Runnable runnable;

    public DebounceTask(Runnable runnable,  Long delay) {
        this.runnable = runnable;
        this.delay = delay;
    }

    /**
     *
     * @param runnable 要执行的任务
     * @param delay 执行时间
     * @return 初始化 DebounceTask 对象
     */
    public static DebounceTask build(Runnable runnable, Long delay){
        return new DebounceTask(runnable, delay);
    }

    //Timer类执行:cancel()-->取消操作;schedule()-->执行操作
    public void timerRun(){
        //如果有任务,则取消不执行(防抖实现的关键)
        if(timer!=null){
            timer.cancel();
        }
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                //把 timer 设置为空,这样下次判断它就不会执行了
                timer=null;
                //执行 runnable 中的 run()方法
                runnable.run();
            }
        }, delay);
    }
}

Debounce-Test 1

Wie Sie sehen können, habe ich während des Tests alle 1 Millisekunde eine Anfrage gestellt. In diesem Fall wird es innerhalb einer Sekunde kontinuierliche Anfragen geben und der Entprellvorgang wird nie ausgeführt.

public static void main(String[] args){
    //构建对象,1000L: 1秒执行-->1秒内没有请求,在执行防抖操作
    DebounceTask task = DebounceTask.build(new Runnable() {
        @Override
        public void run() {
            System.out.println("防抖操作执行了:do task: "+System.currentTimeMillis());
        }
    },1000L);
    long delay = 100;
    while (true){
        System.out.println("请求执行:call task: "+System.currentTimeMillis());
        task.timerRun();
        try {
            //休眠1毫秒在请求
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Das Ergebnis ist wie erwartet:

Verbunden mit der Ziel-VM, Adresse: '127.0.0.1:5437', Transport: 'socket'
Anfrageausführung: Aufrufaufgabe: 1659609433021
Anfrageausführung: Aufrufaufgabe: 1659609433138
Anforderungsausführung: Anrufaufgabe: 1659609433243
Anfrageausführung: Anrufaufgabe: 1659609433350
Anfrageausführung: Anrufaufgabe: 1659609433462
Anfrageausführung: Anrufaufgabe: 16596094336 81
Anforderungsausführung:Aufgabe aufrufen: 1659609433787
Anfrage Ausführung:Aufgabe aufrufen: 1659609433893
Ausführungsanfrage:Aufgabe aufrufen: 1659609433999
Ausführungsanfrage:Aufgabe aufrufen: 1659609434106
Ausführungsanfrage:Aufgabe aufrufen: 1659609434215
Ausführungsanfrage:Aufgabe aufrufen: 165960943432 1
Aufforderung zur Ausführung:Aufgabe aufrufen: 1659609434425
Anforderungsausführung: Aufrufaufgabe: 1659609434534

Anti-Shake-Test 2

In Test 2 lassen wir den Haupt-Thread nach 2 Sekunden Anforderung 2 Sekunden lang ruhen. Zu diesem Zeitpunkt wird der Anti-Shake nicht ausgelöst erneut innerhalb von 1 Sekunde, sodass der Anti-Shake-Betrieb einmalig ausgeführt wird.

public static void main(String[] args){
    //构建对象,1000L:1秒执行
    DebounceTask task = DebounceTask.build(new Runnable() {
        @Override
        public void run() {
            System.out.println("防抖操作执行了:do task: "+System.currentTimeMillis());
        }
    },1000L);
    long delay = 100;
    long douDelay = 0;
    while (true){
        System.out.println("请求执行:call task: "+System.currentTimeMillis());
        task.timerRun();
        douDelay = douDelay+100;
        try {
            //如果请求执行了两秒,我们让他先休息两秒,在接着请求
            if (douDelay == 2000){
                Thread.sleep(douDelay);
            }
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Das Ergebnis ist wie erwartet. Nach Abschluss des Ruhezustands wird die Anforderung kontinuierlich innerhalb einer Millisekunde ausgelöst und die Anti-Shake-Aufgabe wird nicht erneut ausgelöst.

请求执行:call task: 1659609961816
请求执行:call task: 1659609961924
请求执行:call task: 1659609962031
请求执行:call task: 1659609962138
请求执行:call task: 1659609962245
请求执行:call task: 1659609962353
防抖操作执行了:do task: 1659609963355
请求执行:call task: 1659609964464
请求执行:call task: 1659609964569
请求执行:call task: 1659609964678
请求执行:call task: 1659609964784

防抖测试简易版

简易版:根据新手写代码习惯,对代码写法做了调整,但是不影响整体功能。这种写法更加符合我这种新手小白的写法。

public static void main(String[] args){
    //要执行的任务,因为 Runnable 是接口,所以 new 对象的时候要实现它的 run方法
    Runnable runnable =  new Runnable() {
        @Override
        public void run() {
            //执行打印,真实开发中,是这些我们的业务代码。
            System.out.println("防抖操作执行了:do task: "+System.currentTimeMillis());
        }
    };

    //runnable:要执行的任务,通过参数传递进去。1000L:1秒执行内没有请求,就执行一次防抖操作
    DebounceTask task = DebounceTask.build(runnable,1000L);
    //请求持续时间
    long delay = 100;
    //休眠时间,为了让防抖任务执行
    long douDelay = 0;

    //while 死循环,请求一直执行
    while (true){
        System.out.println("请求执行:call task: "+System.currentTimeMillis());
        //调用 DebounceTask 防抖类中的 timerRun() 方法, 执行防抖任务
        task.timerRun();
        douDelay = douDelay+100;
        try {
            //如果请求执行了两秒,我们让他先休息两秒,在接着请求
            if (douDelay == 2000){
                Thread.sleep(douDelay);
            }
            Thread.sleep(delay);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

节流(throttle)

package com.example.test01.zhangch;

import java.util.Timer;
import java.util.TimerTask;

/**
 * @Author zhangch
 * @Description 节流
 * @Date 2022/8/6 15:41
 * @Version 1.0
 */
public class ThrottleTask {
    /**
     * 节流实现关键类
     */
    private Timer timer;
    private Long delay;
    private Runnable runnable;
    private boolean needWait=false;

    /**
     * 有参构造函数
     * @param runnable 要启动的定时任务
     * @param delay 延迟时间
     */
    public ThrottleTask(Runnable runnable,  Long delay) {
        this.runnable = runnable;
        this.delay = delay;
        this.timer = new Timer();
    }

    /**
     * build 创建对象,相当于 ThrottleTask task = new ThrottleTask();
     * @param runnable 要执行的节流任务
     * @param delay 延迟时间
     * @return ThrottleTask 对象
     */
    public static ThrottleTask build(Runnable runnable, Long delay){
        return new ThrottleTask(runnable, delay);
    }

    public void taskRun(){
        //如果 needWait 为 false,结果取反,表达式为 true。执行 if 语句 
        if(!needWait){
            //设置为 true,这样下次就不会再执行
            needWait=true;
            //执行节流方法
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    //执行完成,设置为 false,让下次操作再进入 if 语句中
                    needWait=false;
                    //开启多线程执行 run() 方法
                    runnable.run();
                }
            }, delay);
        }
    }
}

节流测试1

节流测试,每 2ms 请求一次,节流任务是每 1s 执行一次。真实效果应该是 1s 内前端发起了五次请求,但是后端只执行了一次操作

public static void main(String[] args){
    //创建节流要执行的对象,并把要执行的任务传入进去
    ThrottleTask task = ThrottleTask.build(new Runnable() {
        @Override
        public void run() {
            System.out.println("节流任务执行:do task: "+System.currentTimeMillis());
        }
    },1000L);
    //while一直执行,模拟前端用户一直请求后端
    while (true){
        System.out.println("前端请求后端:call task: "+System.currentTimeMillis());
        task.taskRun();
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果如我们所料

前端请求后端:call task: 1659772459363
前端请求后端:call task: 1659772459574
前端请求后端:call task: 1659772459780
前端请求后端:call task: 1659772459995
前端请求后端:call task: 1659772460205
节流任务执行:do task: 1659772460377
前端请求后端:call task: 1659772460409
前端请求后端:call task: 1659772460610
前端请求后端:call task: 1659772460812
前端请求后端:call task: 1659772461027
前端请求后端:call task: 1659772461230
节流任务执行:do task: 1659772461417

彩蛋

idea 爆红线了,强迫症的我受不了,肯定要解决它

So implementieren Sie Anti-Shake und Throttling in Java

解决方法1

脑子第一时间冒出来的是 @SuppressWarnings("all") 注解,跟所有的警告说拜拜~瞬间就清爽了

So implementieren Sie Anti-Shake und Throttling in Java

解决方法2

算了,压制警告总感觉是不负责任。总不能这样草草了事,那就来直面这个爆红。既然让我用 ScheduledExecutorService ,那简单,直接替换

public class ThrottleTask {
    /**
     * 节流实现关键类:
     */
    private ScheduledExecutorService timer;
    private Long delay;
    private Runnable runnable;
    private boolean needWait=false;

    /**
     * 有参构造函数
     * @param runnable 要启动的定时任务
     * @param delay 延迟时间
     */
    public ThrottleTask(Runnable runnable,  Long delay) {
        this.runnable = runnable;
        this.delay = delay;
        this.timer = Executors.newSingleThreadScheduledExecutor();
    }

    /**
     * build 创建对象,相当于 ThrottleTask task = new ThrottleTask();
     * @param runnable 要执行的节流任务
     * @param delay 延迟时间
     * @return ThrottleTask 对象
     */
    public static ThrottleTask build(Runnable runnable, Long delay){
        return new ThrottleTask(runnable, delay);
    }

    public void taskRun(){
        //如果 needWait 为 false,结果取反,表达式为 true。执行 if 语句
        if(!needWait){
            //设置为 true,这样下次就不会再执行
            needWait=true;
            //执行节流方法
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    //执行完成,设置为 false,让下次操作再进入 if 语句中
                    needWait=false;
                    //开启多线程执行 run() 方法
                    runnable.run();
                }
            }, delay,TimeUnit.MILLISECONDS);
        }
    }
}

那么定时器 Timer 和 ScheduledThreadPoolExecutor 解决方案之间的主要区别是什么,我总结了三点...

  • 定时器对系统时钟的变化敏感;ScheduledThreadPoolExecutor并不会。

  • 定时器只有一个执行线程;ScheduledThreadPoolExecutor可以配置任意数量的线程。

  • TimerTask中抛出的运行时异常会杀死线程,因此后续的计划任务不会继续运行;使用ScheduledThreadExecutor–当前任务将被取消,但其余任务将继续运行。

Das obige ist der detaillierte Inhalt vonSo implementieren Sie Anti-Shake und Throttling in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen