目录
一、Timer存在的问题
1、首先 Timer 对调度的支持是基于绝对时间的,而不是相对时间,所以它对系统时间的改变非常敏感。
系统时间修改到当前时间之后,不会影响Timer的执行;但是如果系统时间修改到当前时间之前,就会导致Timer挂起。
说明:
代码取自Timer > mainLoop() 545-556行
currentTime = System.currentTimeMillis();
executionTime = task.nextExecutionTime;
if (taskFired = (executionTime<=currentTime)) {
if (task.period == 0) { // Non-repeating, remove
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
注:period就是TimeTask初始化时设定的执行间隔
从这段代码可以看出,TimerTask执行的条件是(executionTime<=currentTime)。其中executionTime取自TimerTask,而currentTime来自系统时间。currentTime因为修改系统时间而提前了,所以这个条件(executionTime<=currentTime)永远也不会达到,TimerTask将不会被执行。(避免方法:在修改系统时间后重新启动应用)
2、其次 Timer 线程是不会捕获异常的,如果 TimerTask 抛出的了未检查异常则会导致 Timer 线程终止,同时 Timer 也不会重新恢复线程的执行,他会错误的认为整个 Timer 线程都会取消。同时,已经被安排单尚未执行的 TimerTask 也不会再执行了,新的任务也不能被调度。故如果 TimerTask 抛出未检查的异常,Timer 将会产生无法预料的行为。”
3、Timer运行多个TimeTask任务时,只要其中一个挂掉了,其他的TimeTask也不会执行,而ScheduledExecutorService则不会。
二、针对Timer的解决方法
对于 Timer 的缺陷,我们可以考虑 ScheduledThreadPoolExecutor 来替代。Timer 是基于绝对时间的,对系
统时间比较敏感,而 ScheduledThreadPoolExecutor 则是基于相对时间;Timer 是内部是单一线程,而 Sch
eduledThreadPoolExecutor 内部是个线程池,所以可以支持多个任务并发执行。
三、通过ThreadPoolExecutor实现轮询
1、这里封装了一个单例模式的工具类——ExecutorUtil,这是一个初始化线程池的工具类,代码如下:
/**
* Created by wsl
* on 2019/5/10 10:34
* 线程池工具类,用于解决阿里代码检测不允许使用Executors静态方法的问题
*/
public class ExecutorUtil {
/**
* 私有构造函数
*/
private ExecutorUtil() {
}
/**
* 内部类实现单例模式
* 延迟加载,减少内存开销
*/
private static class ExecutorHolder {
private static ExecutorUtil executorUtil = new ExecutorUtil();
}
public static ExecutorUtil getInstance() {
return ExecutorHolder.executorUtil;
}
/**
* 线程池-缓存,可以无限制的创建线程,实现异步
*
* @return ThreadPoolExecutor
*/
public ThreadPoolExecutor newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new CustomThreadFactory());
}
/**
* 线程池-周期执行 使用scheduleAtFixedRate方法可以替代Timer
*
* @param corePoolSize 核心线程数
* @return ScheduledThreadPoolExecutor
*/
public ScheduledThreadPoolExecutor newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize, new CustomThreadFactory());
}
/**
* 线程池-单一线程
* 有且仅有一个工作线程执行任务
* 所有任务按照指定顺序执行,即遵循队列的入队出队规则
*
* @return ThreadPoolExecutor
*/
public ThreadPoolExecutor newSingleThreadExecutor() {
return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new CustomThreadFactory());
}
/**
* the factory to use when the executor creates a new thread
*/
private class CustomThreadFactory implements ThreadFactory {
private AtomicInteger count = new AtomicInteger(1);
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable);
String threadName = ExecutorUtil.class.getSimpleName() + count.addAndGet(1);
thread.setName(threadName);
return thread;
}
}
}
实现轮询,我们需要使用ExecutorUtil的newScheduledThreadPool()方法,在使用处对此方法进行初始化,然后调用下面方法进行轮询:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit)
command:需要执行的任务,是一个Runnable
initialDelay:延迟时间
period:轮询周期
unit:单位,包括:MICROSECONDS:微妙、MILLISECONDS:毫秒、SECONDS:秒等,更多时间单位请参考TimeUnit 类
2、使用,在Activity中使用的例子如下:
private ScheduledThreadPoolExecutor buzzerTimer;
buzzerTimer = ExecutorUtil.getInstance().newScheduledThreadPool(1);
buzzerTimer.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//执行事件
playMusicFromLocal();
}
}, 0, 1000, TimeUnit.MILLISECONDS);
@Override
protected void onDestroy() {
super.onDestroy();
if (buzzerTimer != null && !buzzerTimer.isShutdown()) {
buzzerTimer.shutdown();
}
}
以上是实现轮询的实现方法,延迟时间为0s、周期为1000ms。
如果playMusicFromLocal()要求不能延迟、堵塞(并发),就使用线程池的缓存机制,即上面线程池工具类中的newCachedThreadPool()方法实现异步;如果playMusicFromLocal()不在乎延迟和堵塞,那么不需要用线程池包装,直接添加方法即可。