java中的计划任务Timer工具类提供了以计时器或计划任务的功能来实现按指定时间或时间间隔执行任务,但由于Timer不是以池pool,而是以队列的方式来管理线程的,所以在高并发的情况下运行效率较低,在JDK1.5中提供了 java.util.concurrent 包来进行以线程池为基础的任务调度,在高并发下效率大大提高,几个常用接口和类之间的关系:
Executor是一个执行器,只有一个execute方法:
package java.util.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
public interface Executor {
void execute(Runnable command);
}
ExecutorService是一个执行服务,只要包含的是任务的提交,线程池关闭等操作:
package java.util.concurrent;
import java.util.List;
import java.util.Collection;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
public interface ExecutorService extends Executor {
void shutdown();
List shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
Future submit(Callable task);
Future submit(Runnable task, T result);
Future> submit(Runnable task);
List> invokeAll(Collection extends Callable> tasks) throws InterruptedException;
List> invokeAll(Collection extends Callable> tasks, long timeout, TimeUnit unit)
throws InterruptedException;
T invokeAny(Collection extends Callable> tasks) throws InterruptedException, ExecutionException;
T invokeAny(Collection extends Callable> tasks, long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
AbstractExecutorService抽象类是对ExecutorService的部分方法实现,
ScheduledExecutorService接口继承了ExecutorService接口的一些方法声明,之后又添加了一些自己的调度,定时方法等特性。
ScheduledThreadPoolExecutor便是对ScheduledExecutorService的实现,同时继承至ThreadPoolExecutor,来支持一些非定时任务的执行特性。
其中使用最多的就是ScheduledThreadPoolExecutor,因为非定时任务可以理解成是一个延时为0的定时任务,这里主要说一下ScheduledThreadPoolExecutor的应用。
先说一下实际情况,用AOP来限制用户对接口的访问,在不用Redis的情况下,将以用户的ip和请求的接口连接成的字符串当做key保存在一个Map里,value是用户的请求次数,当用户的访问次数达到限制就禁止访问,并设置定时任务来清除Map中的key。
注解:
/**
* 限制接口访问次数
*
* @see cn.signit.annotation.requestlimit.RequestLimitImpl
* @author xuLiang
* @since 1.0.0
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestLimit {
/**
* 时间段内允许访问的最大次数,默认不限制
*/
int value() default Integer.MAX_VALUE;
/**
* 时间段,单位为秒,默认值60秒
*/
long time() default 60;
String message() default "请求过于频繁";
}
创造一个ScheduledThreadPoolExecutor工厂来返回ScheduledThreadPoolExecutor对象,避免创建多个线程池并且方便Spring容器进行管理:
在实例化ScheduledThreadPoolExecutor的时候用带有ThreadFactory的参数的构造方法,因为没有ThreadFactory的构造方法其实是调用了父类的构造方法,而在父类的构造方法中也会用Executors.defaultThreadFactory()来创造一个默认的线程工厂。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
|| ||
|| ||
|| 调用 ||
|| ||
|| ||
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
可以直接实现ThreadFactory并重写newThread(Runnable r)方法,可以对创建的线程进行命名。
此处的ScheduledThreadPoolExecutorFactory并不是一个标准的工厂模式,因为如果按照标准的工厂模式来设计,每次调用
newExecutor 方法都会返回一个新的ScheduledThreadPoolExecutor对象,这样会浪费很多资源,所以直接在类里面将对象创建好,最后根据需求将对象返回就行了。
ScheduledThreadPoolExecutorFactory中提供两个不同参数的newExecutor方法来设置ScheduledThreadPoolExecutor对象的属性:
newExecutor(int corePoolSize, long keepAliveTime,boolean allowCoreThreadTimeOut)可以设置核心线程的数量,核心线程休眠多久被销毁,以及是否允许核心线程被销毁,当不允许核心线程被销毁即allowCoreThreadTimeOut为false时,keepAliveTime就会失效,值可以随便设置。需要注意executor.setCorePoolSize(corePoolSize);和
executor.setKeepAliveTime(keepAliveTime, TimeUnit.SECONDS);的顺序不能交换,否则会抛出IllegalArgumentException,提示信息为"Core threads must have nonzero keep alive times"。
newExecutor(int corePoolSize)提供了默认的设置,即核心线程不会被销毁。
销毁处于阻塞状态的线程可以节省资源,而让线程保持阻塞状态能随时处理各种任务,要根据实际情况来选择。
/**
* this class is used for example:
* {@code @Autowired ScheduledThreadPoolExecutorFactory executorFactory;}
*
* @author xuLiang
* @since 1.0.0
*/
@Component
public class ScheduledThreadPoolExecutorFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "ScheduledTask");
return thread;
}
private final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(0, this);
public ScheduledThreadPoolExecutor newExecutor(int corePoolSize) {
return newExecutor(corePoolSize, Integer.MAX_VALUE, false);
}
public ScheduledThreadPoolExecutor newExecutor(int corePoolSize, long keepAliveTime,
boolean allowCoreThreadTimeOut) {
executor.setCorePoolSize(corePoolSize);
executor.setKeepAliveTime(keepAliveTime, TimeUnit.SECONDS);
executor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);
return executor;
}
}
实现:
使用@Autowired将ScheduledThreadPoolExecutorFactory交给Spring容器管理。
/**
* 限制接口访问次数
*
* @see cn.signit.annotation.requestlimit.RequestLimit
* @author xuLiang
* @since 1.0.0
*/
@Component
@Aspect
public class RequestLimitImpl {
@Autowired
private HttpServletRequest request;
@Autowired
private ScheduledThreadPoolExecutorFactory executorFactory;
private Map limitMap = new HashMap();
@Pointcut("within(cn.signit.controller.impl..*)")
public void pointCut() {
}
/**
* 限制同一IP访问接口次数
*
* @param joinPoint
* 切点
* @param requestLimit
* 注解
* @throws RequestTooFrequentException
* @see cn.signit.annotation.requestlimit.RequestLimit
* @author xuLiang
* @since 1.0.0
*/
@Before(value = "pointCut() && @annotation(requestLimit)")
public void requestLimit(JoinPoint joinPoint, RequestLimit requestLimit) {
String ip = request.getRemoteAddr().toString();
String url = request.getRequestURL().toString();
String key = "req_limit_".concat(url).concat(ip);
if (limitMap.get(key) == null || limitMap.get(key) == 0) {
limitMap.put(key, 1);
} else {
limitMap.put(key, limitMap.get(key) + 1);
}
int count = limitMap.get(key);
if (count > requestLimit.value()) {
throw new RequestTooFrequentException(requestLimit.message());
}
if (count > 0) {
executorFactory.newExecutor(3, 20, true).schedule(new Runnable() {
@Override
public void run() {
limitMap.remove(key);
}
}, requestLimit.time(), TimeUnit.SECONDS);
}
}
}
最后通过打印executor和executorFactory发现多次请求后两个对象没有被重复创建,用jvisualvm观察到空闲后的线程也被及时销毁了。