Maison >Java >javaDidacticiel >Comment implémenter l'anti-shake et la limitation en Java
Lorsqu'un événement est déclenché en continu et qu'aucun événement n'est déclenché à nouveau dans un certain laps de temps, la fonction de traitement d'événement sera exécutée une fois. Si l'événement est déclenché à nouveau avant l'heure définie, le délai est écoulé. va recommencer.
Anti-shake, c'est-à-dire que si un grand nombre du même événement est déclenché dans un court laps de temps, la minuterie sera réinitialisée. La fonction ne sera exécutée que lorsque l'événement ne sera plus déclenché, puis. attend l'événement spécifié. Et tout ce processus déclenche une fonction similaire sur le serveur. Principe : définir une minuterie et déclencher le traitement d'un événement après une heure spécifiée. La minuterie sera réinitialisée à chaque fois qu'un événement est déclenché.
Exemple : Un exemple très simple est que si vous aimez follement votre cercle d'amis et que vous annulez ensuite les likes, ce processus effacera le minuteur. Attendez que vous en ayez assez de cliquer et arrêtez de cliquer 0,5 seconde avant que la fonction ne soit lancée. déclenché et transmettez vos résultats finaux au serveur.
Question 1 : Si tel est le cas, ne serait-il pas préférable de laisser l'avant faire l'anti-shake ? La réponse est oui, mais l’expérience utilisateur sera perdue. À l'origine, certains utilisateurs l'aimaient juste pour jouer, mais maintenant votre front-end vous indique directement que l'opération est trop rapide ~ veuillez faire une pause. Que les utilisateurs aient perdu leur plaisir, ce point doit également faire référence aux likes de l'espace QQ. Bien que je ne sache pas s'il utilise l'anti-shake, il a des likes et des annulations animés, de sorte qu'à chaque fois que l'utilisateur opère, l'exécution est effectuée. l'animation sautera, ce qui augmente considérablement l'expérience utilisateur.
Question 2 : Voici donc le problème, si vous continuez à cliquer pendant un certain laps de temps, le minuteur sera réinitialisé. Alors s’il clique pendant un jour et une nuit, arrêtera-t-il de l’exécuter ? En théorie, c’est vrai, mais les gens seront fatigués. Vous ne pouvez pas continuer à vous battre, n'est-ce pas ? Les humains ne peuvent donc pas le faire, donc les machines et les scripts ne peuvent que le gérer. L’anti-shake peut également être utilisé pour bloquer certaines attaques de script.
Lorsqu'un événement est déclenché en continu, il est garanti de n'appeler la fonction de traitement d'événement qu'une seule fois au cours d'une certaine période de temps. Cela signifie qu'en supposant qu'un utilisateur continue de déclencher cette fonction et que chaque déclencheur est inférieur au. valeur prédéterminée, la fonction Throttle sera appelée à chaque fois.
En pensant à cela, beaucoup de gens penseront à une fonction, oui, c'est pour éviter les soumissions répétées. Cependant, ce temps d'activité est difficile à contrôler, il est donc recommandé de l'utiliser pour effectuer certaines opérations sans état. Par exemple : lors du rafraîchissement des classements, le front-end semble cliquer tout le temps. En fait, afin d'éviter que l'interface ne plante, le back-end n'effectue un véritable rafraîchissement que toutes les 1 seconde.
anti-shake est de transformer plusieurs exécutions en une seule exécution après le déclenchement dans un délai spécifié.
La limitation consiste à modifier plusieurs exécutions en une durée spécifiée. Peu importe le nombre de fois où elle est déclenchée, elle sera exécutée une fois lorsque le temps est écoulé
La clé de l'implémentation Java de l'anti-tremblement et de la limitation. est la classe Timer et l'interface Runnable.
Parmi elles, les méthodes clés Cancel() dans Timer implémentent l'anti-shake et Schedule() implémentent la limitation. Vous trouverez ci-dessous une brève introduction à ces deux méthodes.
Timer##cancel() : Après l'appel de Timer.cancel(), l'intégralité du thread Timer se terminera.
C'est facile à comprendre. Puisqu'il s'agit d'un anti-shake, tant que vous le déclenchez dans le délai spécifié, je l'annulerai directement(), c'est-à-dire l'annulerai et ne le laisserai pas s'exécuter.
Timer##schedule() : Après que l'utilisateur ait appelé la méthode planning(), il doit attendre N secondes avant d'exécuter la méthode run() pour la première fois.
Ce N est le temps que nous avons évalué en fonction de l'activité et est transmis en paramètre.
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); } }
Comme vous pouvez le voir, pendant le test, j'ai demandé une fois toutes les 1 millisecondes. Dans ce cas, il y aura des requêtes continues dans un délai d'une seconde et l'opération anti-rebond ne sera jamais exécutée.
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(); } } }
Le résultat est comme prévu :
Connecté à la VM cible, adresse : '127.0.0.1:5437', transport : 'socket'
Exécution de la demande : tâche d'appel : 1659609433021
Exécution de la demande : tâche d'appel : 1659609433138
Exécution de la demande : tâche d'appel : 1659609433243
Exécution de la demande : tâche d'appel : 1659609433350
Exécution de la demande : tâche d'appel : 1659609433462
Exécution de la demande : tâche d'appel : 1659609433572
Exécution de la demande : tâche d'appel : 16596094336 81
Exécution de la demande :tâche d'appel : 1659609433787
Demande exécution : tâche d'appel : 1659609433893
Demande d'exécution : tâche d'appel : 1659609433999
Demande d'exécution : tâche d'appel : 1659609434106
Demande d'exécution : tâche d'appel : 1659609434215
Demande d'exécution : tâche d'appel : 1659609434321
Demande d'exécution : appel de tâche : 1659609434425
Exécution de la requête : tâche d'appel : 1659609434534
Dans le test 2, après avoir demandé 2 secondes, nous laissons le thread principal reposer pendant 2 secondes. À ce moment, l'anti-tremblement ne se déclenche pas. à nouveau dans un délai d'une seconde, il sera donc exécuté une fois l'opération anti-tremblement.
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(); } } }
Le résultat est comme prévu. La tâche anti-tremblement est déclenchée une fois. Une fois la mise en veille terminée, la demande sera déclenchée en continu dans un délai d'une milliseconde et l'anti-tremblement ne sera plus déclenché.
请求执行: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(); } } }
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); } } }
节流测试,每 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 爆红线了,强迫症的我受不了,肯定要解决它
脑子第一时间冒出来的是 @SuppressWarnings("all") 注解,跟所有的警告说拜拜~瞬间就清爽了
算了,压制警告总感觉是不负责任。总不能这样草草了事,那就来直面这个爆红。既然让我用 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–当前任务将被取消,但其余任务将继续运行。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!