Home >Java >javaTutorial >How to implement anti-shake and throttling in Java
When an event is continuously triggered, and no event is triggered again within a certain period of time, the event processing function will be executed once. If the event is not triggered before the set time, , the event is triggered again, and the delay starts again.
Anti-shake, that is, if a large number of the same event is triggered in a short period of time, the timer will be reset. The function will not be executed until the event is no longer triggered, and then waits for the specified event. And this whole process triggers a like function to the server. Principle: Set a timer and trigger event processing after a specified time. The timer will be reset every time an event is triggered.
Example: A very simple example is if you like your circle of friends like crazy and then cancel likes, this process will clear the timer, and you will stop clicking when you are tired of clicking. Wait 0.5 seconds before the function is triggered and your final result is sent to the server.
Question 1: If that’s the case, wouldn’t it be better to let the front end do the anti-shake? The answer is yes, but the user experience will be lost. Originally, some users liked it just to play, but now your front-end directly prompts you that the operation is too fast ~ please take a break. Whether users have lost their fun, this point also has to refer to QQ space’s likes. Although I don’t know if it uses anti-shake, it has animated likes and cancellations, so that every time the user operates When running, the execution animation will pop up, which greatly increases the user experience.
Question 2: Then here comes the question. If you keep clicking within a certain period of time, the timer will be reset. Then if he clicks for one day and one night, will he stop executing it? In theory, this is true, but people will be tired. You can't keep fighting, right? So humans can’t do it, so machines and scripts can only handle it. That’s just right. Anti-shake can also be used to block some script attacks.
When an event is continuously triggered, it is guaranteed to only call the event processing function once within a certain period of time. This means that assuming a user keeps triggering This function, and every time the trigger is less than the set value, the function throttling will be called once every this time.
Thinking of this, many people will think of a function, yes, it is to prevent repeated submissions. However, this business time is difficult to control, so it is recommended to use it to do some stateless operations. For example: when refreshing the rankings, the front end seems to be clicking all the time. In fact, in order to prevent the interface from crashing, the back end only performs a real refresh every 1 second.
Anti-shake is to change multiple executions into one execution after triggering within a specified time.
Throttling is to change multiple executions into a specified time. No matter how many times it is triggered, it will be executed once when the time is up.
java realizes anti-shake and throttling The key is the Timer class and Runnable interface.
Among them, the key methods cancel() in Timer implement anti-shake and schedule() implement throttling. Below is a brief introduction to these two methods.
Timer##cancel(): Timer.cancel() will end after the entire Timer thread is called.
This is easy to understand. Since it is anti-shake, as long as you trigger it within the specified time, I will directly cancel() it, that is, cancel it and not let it execute.
Timer##schedule(): After the user calls the schedule() method, he has to wait for N seconds before executing the run() method for the first time.
This N is the time we evaluated based on the business and is passed in as a parameter.
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); } }
As you can see, during the test, I requested once every 1 millisecond. In this case, there will be continuity within 1 second. Requested, the anti-shake operation is never performed.
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(); } } }
The result is as we expected:
Connected to the target VM, address: '127.0.0.1:5437', transport: 'socket'
Request execution: call task: 1659609433021
Request execution: call task: 1659609433138
Request execution: call task: 1659609433243
Request execution: call task: 1659609433350
Request execution: call task: 1659609433462
Request execution: call task: 1659609433572
Request execution: call task: 1659609433681
Request execution: call task: 1659609433787
Request execution: call task: 1659609433893
Request execution: call task: 1659609433999
Request execution: call task: 1659609434106
Request execution: call task: 1659609434215
Request execution: call task: 1659609434321
Request execution: call task: 1659609434425
Request execution: call task: 1659609434534
In Test 2, after requesting for 2 seconds, we let the main thread rest for 2 seconds. At this time, the anti-shake is not triggered again within 1 second, so it will be executed once. Anti-shake operation.
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(); } } }
The result is as expected. The anti-shake task is triggered once. After the sleep is completed, the request will be triggered continuously within 1 millisecond, and the anti-shake will not be triggered again.
请求执行: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–当前任务将被取消,但其余任务将继续运行。
The above is the detailed content of How to implement anti-shake and throttling in Java. For more information, please follow other related articles on the PHP Chinese website!