子程序(SubRoutine)在BJAF框架中的定义是专门用来处理某一次的任务计算,处理完就结束。它本质上也是一个线程,只是这个线程执行一次就结束。另外,BJAF框架了,针对子程序实际运行情况,还实现了一个针对子程序任务执行的超时处理机制,用来解决由于某个任务长时间运行(超过预估的时间,或者死循环,阻塞挂起等)而无法及时线程回收的技术难题。
对于子程序,BJAF框架提供了线程池来优化其执行效率,同时对子程序的执行方式做了封装,(RoutineExecutor)提供做多不同执行方式。参见下面类图:
子程序说明示例
SubRoutine类说明如下:
方法与属性 | 功能说明 |
SubRoutine(maxBlockTime : int) | 构造函数,调用此构造函数实例化一个子程序,代表此子程序自动参与后台线程超时机制的监控。参数:maxBlockTime就是允许最大阻塞时间,单位为秒(S);如果任务执行超过这个时间,框架会对这个子程序进行超时中断处理。 |
SubRoutine() | 构造函数,调用此构造函数实例化一个子程序,不参与后台线程超时机制监控。 |
routine() : void | 子程序运行,为抽象方法,子程序执行任务技术所必须实现。 |
setResult(result : Object) : void | 设置子程序返回结果 routine方法体内调用才有效。适合需要返回结果的子程序(带超时执行机制) 由RoutineExecutor.runRoutineForResult方法执行 |
例如,建一个简单子程序,处理任务是打印一下当前时间戳。代码如下:
package example.appsrv.routine;
import com.beetle.framework.appsrv.SubRoutine;
public class DemoRoutine extends SubRoutine {
public DemoRoutine() {
super();
}
protected void routine() throws InterruptedException {
System.out.println(System.currentTimeMillis() + "->do something...");
}
}
利用RoutineExecutor子程序执行:
package example.appsrv.routine;
import com.beetle.framework.appsrv.RoutineExecutor;
public class TestClient {
public static void main(String[] args) {
RoutineExecutor.runRoutineInPool(new DemoRoutine());
}
}
执行结果:
loaded /config/log4j.properties from file
1235541073513->do something...
下面演示一下,子程序超时回收功能。假设,上面的DemoRoutine在开发的时候不小心写了个死循环,导致子程序长时间吊死。此时,我们能够及时回收此子程序线程就显得很有必要了。
package example.appsrv.routine;
import com.beetle.framework.appsrv.SubRoutine;
public class DemoRoutine extends SubRoutine {
public DemoRoutine(int maxBlockTime) {
super(maxBlockTime);// 采取超时参数构造函数
}
protected void routine() throws InterruptedException {
while (true) {// 死循环
System.out.println(System.currentTimeMillis() + "->do something...");
}
}
}
执行此子程序:
package example.appsrv.routine;
import com.beetle.framework.appsrv.RoutineExecutor;
public class TestClient {
public static void main(String[] args) {
RoutineExecutor.runRoutineInPool(new DemoRoutine(5));// 最大阻塞时间为5秒
}
}
执行结果:
loaded /config/log4j.properties from file
1235551942465->do something...
.....//省略重复输出
1235551942465->do something...
1235551942465->do something...
com.beetle.framework.appsrv.RoutineRunException: thread interrpting
at com.beetle.framework.appsrv.SubRoutine.run(SubRoutine.java:114)
at com.beetle.framework.appsrv.RoutinesPool$ThreadPoolExecutor$Worker.run(RoutinesPool.java:795)
at java.lang.Thread.run(Thread.java:534)
5190 DEBUG [Thread-1] com.beetle.framework.appsrv.RoutinesPool$RoutineMonitor - Thread:[gcaHS0gnMy]killed!--
可见超过5秒,框架后台监控服务就会把这个死循环的子程序(线程)给及时中断回收。
执行方式说明及示例
从图1类图中,可知RoutineExecutor提供了以下执行方式:
方法与属性 | 功能说明 |
runRoutineDirect(subRoutine: SubRoutine) : void | 直接执行,为静态方法。不采取线程池,直接创建线程执行。除了直接执行方法外,执行器所有的执行方法都会将子程序放在线程池内执行 |
runRoutineInPool(subRoutine: SubRoutine) : void | 在线程池中,执行此子程序。 |
addSubRoutine(subRoutine: SubRoutine) : void | 往执行器中,添加一个子程序。(执行器,支持多个子程序一起执行) |
runRoutineEarly() : void | 提早执行子程序,后调用getResult方法获取运算结果 特别适合在主流程中提前先处理任务重部分,再处理其它任务,最后再获取重任务计算结果的场景。这样处理的最大好处是优化和节约主流程的执行时间 |
getResult() : Object | 获取此子程序的处理结果(此方法会阻塞) |
runRoutineForTime() : Object | 执行子程序并等待返回其计算结果。 根据此子程序设置最大阻塞时间来防止线程超时,则超出此时间会中断此子程序 并触发子程序的terminate()事件(方法) @return 子程序结果 |
runRoutineForTime(time : int) : Object | 同上。 只是支持自定义等待时间 |
runRoutineParalleJoin() : void | 并行执行子程序,并等待所有的子程序结束后再退出 (针对一组子程序,此方法会阻塞) (没有超时处理机制,即使子程序设置最大阻塞时间也无效) |
runRoutineInTurn() : void | 依次执行子程序(按照顺序前一个子程序运行完毕才接着运行下一个,直到所有的子程序执行完毕) (针对一组子程序) |
常规执行一个子程序前面代码已经演示过,下面示例一下其它特别执行方式:
Ø针对一组子程序,并行执行,并等待所有子程序结束后再返回
构建3个子程序,代码分别为,SR1代码
package example.appsrv.routine;
import com.beetle.framework.appsrv.SubRoutine;
public class SR1 extends SubRoutine {
protected void routine() throws InterruptedException {
System.out.println("sr1-begin");
sleep(3000);
System.out.println("sr1-end");
}
}
SR2代码:
package example.appsrv.routine;
import com.beetle.framework.appsrv.SubRoutine;
public class SR2 extends SubRoutine {
protected void routine() throws InterruptedException {
System.out.println("sr2-begin");
sleep(2000);
System.out.println("sr2-end");
}
}
SR3代码:
package example.appsrv.routine;
import com.beetle.framework.appsrv.SubRoutine;
public class SR3 extends SubRoutine {
protected void routine() throws InterruptedException {
System.out.println("sr3-begin");
sleep(1000);
System.out.println("sr3-end");
}
}
编写执行客户端代码:
package example.appsrv.routine;
import com.beetle.framework.appsrv.RoutineExecutor;
public class TestParalleClient {
public static void main(String[] args) {
// 构建一个子程序执行器
RoutineExecutor re = new RoutineExecutor();
re.addSubRoutine(new SR1());// 添加各个子程序到执行器队列
re.addSubRoutine(new SR2());
re.addSubRoutine(new SR3());
re.runRoutineParalleJoin();// 并行执行,并阻塞,等待队列中所有子程序都结束才返回
System.out.println("ok");
}
}
执行结果:
sr1-begin
sr3-begin
sr2-begin
sr3-end
sr2-end
sr1-end
ok
此模型对于哪些计算量很巨大任务的处理很有帮助,我们可以把此任务按照一定的条件,分解成多个子任务,并行处理,从而加快任务处理速度。
Ø针对一组子程序,依次串行执行,并等待所有子程序结束后再返回
沿用前面的3个子程序,串行执行的客户端代码如下:
package example.appsrv.routine;
import com.beetle.framework.appsrv.RoutineExecutor;
public class TestInTurnClient {
public static void main(String[] args) {
// 构建一个子程序执行器
RoutineExecutor re = new RoutineExecutor();
re.addSubRoutine(new SR1());// 添加各个子程序到执行器队列
re.addSubRoutine(new SR2());
re.addSubRoutine(new SR3());
re.runRoutineInTurn();// 串行执行,并阻塞,等待队列中所有子程序都结束才返回
System.out.println("ok");
}
}
执行结果:
sr1-begin
sr1-end
sr2-begin
sr2-end
sr3-begin
sr3-end
ok
执行器还提供了一个runRoutineInTurnNoBlock()方法,不会阻塞主流程,让其在后台串行执行。
Ø先执行,后拿结果
当我们在主流程中处理多个任务,若这些任务中,有某个计算量很大,十分消耗时间,为了提高主流程的处理速度,我们可以把这个任务封装成子程序,先执行,主流程处理完其它任务后,再获取这个任务的结果。
示例代码如下:
编写一个HardWorkSR子程序:
package example.appsrv.routine;
import java.util.ArrayList;
import java.util.List;
import com.beetle.framework.appsrv.SubRoutine;
public class HardWorkSR extends SubRoutine {
protected void routine() throws InterruptedException {
System.out.println("work-begin");
List data = new ArrayList();
sleep(10000);// 假设此任务要计算10秒
data.add("AAA");
data.add("BBB");
this.setResult(data);// 设置结果以便返回
System.out.println("word-end");
}
}
编写客户端:
package example.appsrv.routine;
import java.util.List;
import com.beetle.framework.appsrv.RoutineExecutor;
public class TestEarlyClient {
public static void main(String[] args) {
RoutineExecutor re = new RoutineExecutor();
re.addSubRoutine(new HardWorkSR());
re.runRoutineEarly();// 提早执行
// ...继续处理其它任务
System.out.println("do other work...");
// 处理完其它任务后,再来拿结果
List result = (List) re.getResult();// 会阻塞一定等到任务处理完毕有结果返回为止
System.out.println(result);
System.out.println("ok");
}
}
执行过程,参考一下顺序图:
执行结果如下:
do other work...
work-begin
word-end
[AAA, BBB]
ok
Ø具备超时保护并能获取结果的执行
沿用上面的HardWorkSR子程序(其计算时间为10s),参考以下执行方式的区别:
package example.appsrv.routine;
import java.util.List;
import com.beetle.framework.appsrv.RoutineExecutor;
public class TestTimeoutClient {
public static void main(String[] args) {
RoutineExecutor re = new RoutineExecutor();
re.addSubRoutine(new HardWorkSR());
List result = (List) re.runRoutineForTime(11);//最大允许子程序执行11秒
System.out.println(result);
System.out.println("ok");
}
}
从代码可知,超时设置为11秒,HardWorkSR子程序会正常执行,不会做超时处理,此时,其运行结果如下:
work-begin
word-end
[AAA, BBB]
ok
若把上面的时间设置为5秒,如:
List result = (List) re.runRoutineForTime(5);// 最大允许子程序执行5秒
由于HardWorkSR本身需要10秒才能计算完成,而我们5秒就要回收,所以其会被超时处理,此时,其运行结果如下:
work-begin
com.beetle.framework.appsrv.RoutineRunException: thread timeout; cause exception is:
EDU.oswego.cs.dl.util.concurrent.TimeoutException
EDU.oswego.cs.dl.util.concurrent.TimeoutException
at EDU.oswego.cs.dl.util.concurrent.FutureResult.timedGet(FutureResult.java:128)
at com.beetle.framework.appsrv.RoutineExecutor.runRoutineForTime(RoutineExecutor.java:104)
at example.appsrv.routine.TestTimeoutClient.main(TestTimeoutClient.java:12)
线程超时,执行中断。