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

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

WBOY
풀어 주다: 2023-05-12 20:34:04
앞으로
1216명이 탐색했습니다.

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으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿