>Java >java지도 시간 >Java에서 손떨림 방지 및 조절을 구현하는 방법

Java에서 손떨림 방지 및 조절을 구현하는 방법

WBOY
WBOY앞으로
2023-05-12 20:34:041208검색

Concept

Debounce

이벤트가 계속 발생하고, 일정 시간 내에 다시 이벤트가 발생하지 않을 경우, 설정된 시간이 되기 전에 이벤트가 다시 발생하면 이벤트 처리 기능이 1회 실행됩니다. 시간이 다시 시작됩니다.

  • 손떨림 방지, 즉 짧은 시간 내에 동일한 이벤트가 많이 발생하면 해당 이벤트가 더 이상 발생하지 않을 때까지 타이머가 재설정됩니다. 지정된 이벤트를 기다립니다. 그리고 이 전체 프로세스는 서버에 유사한 기능을 트리거합니다. 원리: 타이머를 설정하고 지정된 시간 후에 이벤트 처리를 트리거합니다. 타이머는 이벤트가 트리거될 때마다 재설정됩니다.

  • 예: 매우 간단한 예는 친구들의 서클을 미친 듯이 좋아한 다음 좋아요를 취소하는 경우입니다. 이 프로세스는 클릭이 지칠 때까지 기다렸다가 클릭을 중지하는 것입니다. Triggered. 최종 결과를 서버에 전달합니다.

  • 질문 1: 그렇다면 앞부분에 손떨림 방지를 시키는 게 낫지 않을까요? 대답은 '예'입니다. 하지만 사용자 경험은 상실됩니다. 원래 일부 유저들은 그냥 플레이하는 걸 좋아했는데, 이제는 작업이 너무 빠르다고 프런트 엔드에서 직접 메시지가 뜹니다~ 잠시 쉬어가세요. 사용자가 재미를 잃었는지 여부는 QQ 공간의 좋아요도 참조해야합니다. 흔들림 방지 기능을 사용하는지 여부는 알 수 없지만 애니메이션 좋아요 및 취소 기능이 있으므로 사용자가 실행할 때마다 실행됩니다. 애니메이션이 팝업되어 사용자 경험이 크게 향상됩니다.

  • 질문 2: 문제는 다음과 같습니다. 특정 시간 내에 계속 클릭하면 타이머가 재설정됩니다. 그러면 하루 밤낮으로 클릭하면 실행이 중단되나요? 이론적으로는 이것이 사실이지만 사람들은 피곤할 것입니다. 계속 싸울 수는 없겠죠? 따라서 인간은 할 수 없으므로 기계와 스크립트만 처리할 수 있습니다. 흔들림 방지를 사용하여 일부 스크립트 공격을 차단할 수도 있습니다.

Throttle

이벤트가 지속적으로 트리거되면 특정 기간 내에 이벤트 처리 함수를 한 번만 호출하는 것이 보장됩니다. 이는 사용자가 이 기능을 계속 트리거하고 각 트리거가 이벤트 처리 함수보다 적다는 것을 의미합니다. 미리 결정된 값이 있으면 Throttle 함수가 매번 호출됩니다.

이렇게 생각하면 많은 분들이 함수를 떠올리실텐데요, 네, 중복 제출을 방지하는 기능입니다. 하지만 이 업무 시간은 통제하기 어렵기 때문에 이를 사용하여 일부 Stateless 작업을 수행하는 것이 좋습니다. 예를 들어 순위를 새로 고칠 때 프런트 엔드는 항상 클릭하는 것처럼 보입니다. 실제로 인터페이스 충돌을 방지하기 위해 백 엔드는 1초마다 실제 새로 고침을 수행합니다.

차이점

흔들림 방지는 지정된 시간 내에 트리거된 후 여러 실행을 한 번의 실행으로 변경하는 것입니다.

스로틀링은 여러 번의 실행을 지정된 시간으로 변경하는 것입니다.

Java 구현

Java의 흔들림 방지 및 스로틀링 구현의 핵심입니다. Timer 클래스와 Runnable 인터페이스입니다.

그 중 Timer의 핵심 메소드인 cancel()은 손떨림 방지를 구현하고, Schedule()은 조절을 구현합니다. 다음은 이 두 가지 방법에 대한 간략한 소개입니다.

Timer##cancel(): Timer.cancel()이 호출된 후 전체 타이머 스레드가 종료됩니다.

이것은 이해하기 쉽습니다. 흔들림 방지 기능이므로 지정된 시간 내에 트리거하면 직접 취소()합니다. 즉, 취소하고 실행하지 못하게 합니다.

Timer##schedule(): 사용자가 Schedule() 메서드를 호출한 후 처음으로 run() 메서드를 실행하기 전에 N초를 기다려야 합니다.

이 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();
        }
    }
}

결과는 예상한 대로입니다.

대상 VM에 연결됨, 주소: '127.0.0.1:5437', 전송: '소켓'
요청 실행: 호출 작업: 1659609433021
요청 실행: 호출 작업: 1659609433138
요청 실행: 태스크 호출: 1659609433243
요청 실행: 태스크 호출: 1659609433350
요청 실행: 태스크 호출: 1659609433462
요청 실행: 태스크 호출: 1659609433572
요청 실행: 태스크 호출: 1659609433681
실행 요청:작업 호출: 1659609433787
요청 실행 :호출 작업: 1659609433893
실행 요청:작업 호출: 1659609433999
실행 요청:작업 호출: 1659609434106
실행 요청:작업 호출: 1659609434215
실행 요청:작업 호출: 1659609434321
실행 요청:작업 호출: 1659609434425
요청 실행: 호출 작업: 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으로 문의하시기 바랍니다. 삭제