新功能更新完毕,坐等测试妹妹反馈测试结果,心里美滋滋。
登上网页打开bugly,查看最近的bug情况。咦,情况不对呢,怎么又出现这个bug了,上次出现NullPointerException,我已经非空判断了,怎么又出现NullPointerException,这就很难受了。
于是仔仔细细检查代码,网络请求,数据更新,一遍下来,没问题呀,why?没有代码神兽保佑?
百思不得其解呀,咦,有情况,这个界面的网络请求好像是轮询(本项目有两个人开发,刚好这里不是我开发的,恰好那哥们请假了),轮询,emmmmmm,并发???????有没有可能是并发呢。带着问题重新去看代码,查看网络请求的触发。有意思,果然是轮询,还使用的是Timer。
Timer,这个嘛,定时任务,但是以前没玩过。没玩过不要紧,我们可以学嘛。
Timer的基本使用方法:
Timer mTimer = new Timer();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
//执行具体的任务
}
}, 1000, 1000);
我们看到了schedule方法,第一次参数是执行的任务,第二次参数是第一次启动Timer的延时,第三个参数是轮询的间隔时间。
Timer是定时任务,怎么就会引发并发了呢?这个时候解决问题的方法就是,让我们大声的说出来“看~~源~~码~~”。确实,很多问题的解决方法,在源码中其实都已经为我们提供了解决方法。
我们进入Timer,我们首先都看到了两个东东
private final TaskQueue queue = new TaskQueue();
private final TimerThread thread = new TimerThread(queue);
我们先看第一个东东是什么鬼。
第一个东东,从类的名字我们可以知道,是任务队列(好的命名在这个时候起了很大的关键作用),TaskQueue中定义了一个长度为128的TimerTask数组。TaskQueue类中的所有方法就是对TimerTask数字的操作,比如添加消息,移除消息等。TimerTask又是继承的Runnable接口。
TaskQueue就是维持的继承Runnable接口的消息队列。
我们再回到Timer的构造方法,Timer一共有四个构造方法。
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
public Timer(String name) {
thread.setName(name);
thread.start();
}
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
从构造方法中我们可以看出,最终都会启动一个异步线程,这个异步线程就是我们的第二个东东,TimerThread。
TimerThread集成Thread,在run方法中调用了mainLoop方法,我们进入mainLoop方法,此方法是整个Timer的关键。
private void mainLoop() {
//开启无限循环
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// 如果消息队列为空,线程还活着,就堵塞线程
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
//如果消息队列为空,线程已经停止了,比如调用cancel(),就终止循环
if (queue.isEmpty())
break;
long currentTime, executionTime;
//获取消息队列顶部的消息
task = queue.getMin();
synchronized(task.lock) {
//如果消息已经被取消,则移除次消息,并进入下一次循环
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue;
}
//获取系统当前时间
currentTime = System.currentTimeMillis();
//获取任务执行的时间
executionTime = task.nextExecutionTime;
//判断是否已经到了该执行的时间
if (taskFired = (executionTime<=currentTime)) {
//如果轮询的时间差为0,就移除,并更改状态为已执行
if (task.period == 0) {
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else {
//更新轮询时间,为下一次轮询设置时间
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
//如果还没有到需要轮询的时间,计算出时间差,堵塞对应的时间
if (!taskFired)
queue.wait(executionTime - currentTime);
}
//已经到了需要执行的时间,开始执行任务
if (taskFired)
task.run();
} catch(InterruptedException e) {
}
}
}
从上面源码中我们可以看出,Timer,到点之后只管执行任务,不管任务执行的过程和结果。这就容易导致并发问题。
https://mp.csdn.net/postedit/80297254
以上均为酒后胡言,如果有不对请各位大佬,批评教育。