我首次接触到节流阀是编写前端的时候遇到的,当时的需求是给Javascript
的window.onsize
事件绑定函数,当浏览器页面大小被改变时调用,但是存在一个问题:用鼠标拖拽的形式改变浏览器页面大小的过程是连续的,也就是window.onsize
事件在短时间内被频繁触发。当回调频繁执行时,页面可能会变卡,所以此时节流阀解决了这一个问题。
比如有个按钮,每隔
1ms
按下一次。如果设置执行频率为10ms
执行一次,那么连续按下按钮,也只会10ms
按钮内回调事件生效一次。同时为了保证最后一次生效、或者比较长的时间内按钮只按下了一次,则这一次也需要生效。
这里先贴一个Javascript
版本的节流阀代码
//节流阀
export function throttle(delay,callback) {
let startTime = new Date().getTime();
let timer = null;
return function () {
const currentTime = new Date().getTime();
clearTimeout(timer)
//console.log("时间差:", currentTime - startTime);
if (currentTime - startTime >= delay) {
callback();
startTime = currentTime; // 注意:是startTime=curerntTime!
// 不是currentTime=startTime
} else {
timer = setTimeout(function () {
callback();
}, delay)
}
}
}
参数说明
delay
:连续调用时的执行频率,单位为ms
callback
:被调用的回调函数
代码解读:
这种写法使用的
Javascript
闭包,函数内部的startTime
和timer
并没有因为函数调用结束之后被销毁,还能够继续被return function(){...}
内部的函数使用(可能产生内存泄漏)。
当返回的function
被调用的时候,会获取当前时间,同时销毁上一次调用时创建的延迟函数,计算当前时间与上一次被调用时(或初始化时的时间)时间差是否大于等于delay
,若是则执行回调,更新上一次被调用的时间,若不是,则新建延迟函数。
延时函数主要用于函数只调用一次以及最后一次调用都会被执行
由于
Javascript
是单线程的,不需要考虑线程安全问题,而使用Java编写节流阀需要考虑这个。
为了能够使用Javascript里面接收function
参数和return function
返回值写法,需要使用Java1.8
的Lambda
语法糖和函数式接口
涉及知识点:
接口代码:
package lambda;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@FunctionalInterface
public interface Throttle{
/**
*
* @param callback 回调函数
* @param delay 流速控制时间,单位毫秒
* @return 使用lambda和闭包构造的匿名节流阀对象
*/
static Throttle bulid(Throttle callback,long delay){
AtomicLong startTime= new AtomicLong(System.currentTimeMillis());
AtomicReference<Thread> thread= new AtomicReference<>();
return () -> {
final long currentTime=System.currentTimeMillis();
synchronized (callback){
if(thread.get() !=null) thread.get().interrupt();
if(currentTime - startTime.get() >=delay){
callback.callback();
startTime.set(currentTime);
}else{
thread.set(new Thread(() -> {
try {
Thread.sleep(delay);
} catch (InterruptedException ignored) {
return;
}
callback.callback();
}));
thread.get().start();
}
}
};
}
//执行节流阀
void callback();
}
使用方法
public class ThrottleTest {
public static void main(String[] args) {
//创建:1.使用匿名内部类写法
Throttle bulid = Throttle.bulid(new Throttle() {
@Override
public void callback() {
//TODO
}
}, 100);
//创建:2.使用lambda写法
bulid = Throttle.bulid(() -> {
//TODO
}, 100);
//调用
bulid.callback();
}
}
测试
package lambda;
public class ThrottleTest {
public static void main(String[] args) {
ThrottleTest test= new ThrottleTest();
test.test01();
//test.test02();
}
public void test01(){
Throttle throttle = Throttle.bulid(() -> System.out.println(System.currentTimeMillis()%1000), 50);
int count=0;
while(count++<500){
throttle.callback();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void test02(){
Throttle throttle = Throttle.bulid(() -> System.out.println(System.currentTimeMillis()%1000), 50);
Thread[] threads=new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
int count = 0;
while (count++ < 500) {
throttle.callback();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
for (Thread thread : threads) {
thread.start();
}
}
}
test01()
执行结果:
282
333
385
436
486
536
587
637
688
740
790
841
891
941
991
42
92
143
244
test02()
执行结果:
898
948
999
49
99
149
199
249
299
350
400
450
500
550
600
650
700
750
800
850
930