首頁 > Java > java教程 > 主體

Java中的防手震和節流如何實現

WBOY
發布: 2023-05-12 20:34:04
轉載
1166 人瀏覽過

概念

防手震(debounce)

當持續觸發事件時,一定時間段內沒有再觸發事件,事件處理函數才會執行一次,如果設定時間到來之前,又觸發了事件,就重新開始延時。

  • 防手震,也就是如果短時間內大量觸發相同事件,都會重置計時器,等到事件不觸發了,再等待規定的事件,才會執行函數。而這整個過程就觸發了一次按讚函數到伺服器。原理:設定計時器,設定在規定的時間後觸發事件處理,每次觸發事件都會重設計時器。

  • 舉例:很簡單的例子,就是如果你瘋狂的給朋友圈讚再取消點贊,這個過程都會把計時器清空,等到你點累了不點了,等待0.5秒,才會觸發函數,把你最終結果傳給伺服器。

  • 問題1:那既然是這樣,讓前端做防手不就好了嘛。答案是可以,但是會失去使用者體驗。原本有的用戶按讚就是為了玩,現在你前端直接提示操作太快~請歇會。用戶是不是就失去了樂趣,這一點還得參考QQ空間的點贊,雖然我不知道它是不是用了防抖,但是他把點贊,取消點讚做成了動畫,這樣每次用戶操作的時候,都會跳脫執行動畫,大大增加了使用者的體驗性。

  • 問題2:那麼問題來了,在一定時間內,一直點擊,就會重置計時器。那要是點擊一天一夜,是不是他就不會在執行了。理論上是這樣,但是人會累的嘛。總不能一直戰鬥是吧。所以人做不到,只能是機器、腳本來處理了,那也正好,防手震還能用來阻擋部分腳本攻擊。

節流(throttle)

當持續觸發事件時,保證在一定時間內只呼叫一次事件處理函數,意思是說,假設一個使用者一直觸發這個函數,且每次觸發小於既定值,函數節流會每隔這個時間呼叫一次。

想到這裡,很多人會想到一​​個作用,沒錯,就是防重複提交。但這個業務時間比較難把控,所以還是建議用它來做一些無狀態的操作比較好。比如說:刷新排行榜,前端看似一直在點擊,其實後端為了防止介面崩掉,每1秒才執行真正的一次刷新。

區別

防手震是將多次執行變成指定時間內不在觸發之後,執行一次。

節流是將多次執行變成指定時間不論觸發多少次,時間一到就執行一次

Java實作

java實作防手震和節流的關鍵是Timer類別和Runnable介面。

其中,Timer中關鍵方法cancel() 實作防手震 schedule() 實作節流。下面簡單介紹一下這兩個方法。

Timer##cancel():Timer.cancel() 被呼叫之後整個Timer 的 執行緒都會結束掉。

這就很好理解了,既然是做防抖,只要你在指定時間內觸發,我直接 cancel()掉,就是取消掉,不讓他執行。

Timer##schedule():使用者呼叫 schedule() 方法後,要等待N秒的時間才可以第一次執行 run() 方法。

這個N是我們根據業務評估出來的時間,作為參數傳進去。

防手震(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);
    }
}
登入後複製

防手震測試1

#可以看到,測試中,我1 毫秒請求一次,這樣的話,1秒內都存在連續請求,防手震操作永遠不會執行。

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();
        }
    }
}
登入後複製

結果如我們所料:

Connected to the target VM, address: '127.0.0.1:5437', transport: 'socket'
請求執行:call task: 1659609433021
請求執行:call task: 1659609433138
請求執行:call task: 1659609433243
請求執行:call task: 165960943335243
請求執行:call task: 16596094335060212020242: 請求執行all task: 1659609433572
請求執行:call task: 1659609433681
請求執行:call task: 1659609433787
請求執行:call task: 165960943389609438936232: 請求執行all task: 1659609434106
請求執行:call task: 1659609434215
請求執行:call task: 1659609434321
請求執行:call task: 1659609434425
請求執行:call task: 1659609434534


防手震測試2

測試2中,我們在請求了2秒之後,讓主執行緒休息2秒,這個時候,防手震在1秒內沒有在次觸發,所以就會執行一次防手震操作。

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();
        }
    }
}
登入後複製

結果如我們所料,防抖任務觸發了一次,休眠結束之後,請求就會在1毫秒內連續觸發,防手震也就不會在次觸發了。

请求执行: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 爆红线了,强迫症的我受不了,肯定要解决它

Java中的防手震和節流如何實現

解决方法1

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

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–当前任务将被取消,但其余任务将继续运行。

以上是Java中的防手震和節流如何實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:yisu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板