并发编程有必要先了解 目前业务场景常见的问题:
问题2 :线程处理 多任务问题时对异常的不同处理
package com.gary.javastack.concurrent.aqs.safe;
import java.util.Timer;
import java.util.TimerTask;
/**
* 当一个Timer运行多个TimerTask时,只要其中一个TimerTask在执行中
* 向run方法外抛出异常时,则其他任务也会自动终止。
*
* 推荐使用ScheduledThreadPoolExecutor的schedule,如果其中的一个
* 任务抛出异常,其他任务则不受影响。之所以ScheduledThreadPoolExecutor
* 的其他任务不受抛出异常的任务的影响,是因为在ScheduledThreadPoolExecutor中的
* ScheduledFutureTask任务中catch掉了异常,但是在线程池任务的run方法内使用catch
* 捕获异常并打印日志是最佳实战。
*/
public class TimerInterruptQue {
// 创建定时器对象
static Timer timer = new Timer();
public static void main(String[] args) {
// 添加任务1,延迟500ms执行
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("------one Task-------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new RuntimeException("error");
}
},500);
// 添加任务2,延迟1000ms执行
timer.schedule(new TimerTask() {
@Override
public void run() {
for (;;){
System.out.println("------two Task-------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},1000);
}
}
执行效果截图:
解决方案的代码演示:
package com.gary.javastack.concurrent.aqs.safe;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestScheduledThreadPoolExecutor {
static ScheduledThreadPoolExecutor poolExecutor = new ScheduledThreadPoolExecutor(1);
public static void main(String[] args) {
poolExecutor.schedule(new Runnable() {
@Override
public void run() {
System.out.println("--------one Task----------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
throw new RuntimeException("error");
}
}, 500, TimeUnit.MICROSECONDS);
poolExecutor.schedule(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("--------two Task----------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("");
}
}, 1000, TimeUnit.MICROSECONDS);
poolExecutor.shutdown();
}
}
执行效果截图:
总结:
- ScheduledThreadPoolExecutor的处理方式:
ScheduledThreadPoolExecutor
的其他任务不受抛出异常的任务的影响,是因为在ScheduledThreadPoolExecutor中的
ScheduledFutureTask任务中catch掉了异常
- Timer的处理方式如下:
具体处理逻辑 mainLoop()
/**
* The main timer loop. (See class comment.)
*/
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
task = queue.getMin();
synchronized(task.lock) {
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
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);
}
}
}
if (!taskFired) // Task hasn't yet fired; wait
queue.wait(executionTime - currentTime);
}
if (taskFired) // Task fired; run it, holding no locks
task.run();
} catch(InterruptedException e) {
}
}
}
}
当任务在执行过程中抛出除了InterruptedExeception之外的异常时,唯一的消费线程TimerThread就会因为抛出异常而终止,那么任务队列中的其他执行的任务就会被清除掉,所以在TimerTask的run方法内最好使用try
catch结构捕获可能的异常,不要把异常抛出到run方法之外。
编码建议:
ScheduledThreadPoolExecutor是jdk并发包提供的组件,其功能包含但不限于Timer。Timer是固定的多线程生产任务
单线程消费任务,但是ScheduledThreadPoolExecutor是可以配置的,既可以是多线程生产单线程消费模式也可以是多线程生产多线程消费,所以在日常开发中使用定时器功能时应该优先使用ScheduledThreadPoolExecutor