使用线程执行器执行有返回值的线程
有返回值的线程定义需要继承接口Callable,然后将线程交给执行器执行,提交线程需要使用submit方法。
例如,某驾校有4辆考试用车,有8个学生要进行考试,要求分别获得考生的考试时间。在本例中,可以将驾校看作是一个线程池,4辆车看作是可以同时执行任务的线程,8个考生看作是线程要执行的对象。
代码示例:
public class Student implements Callable<Long> {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public Long call() throws Exception {
System.out.println(name + "开始考试");
long start = System.currentTimeMillis();
int num = (int) (Math.random() * Integer.MAX_VALUE);
for(int i = 0; i < num; i++);
long end = System.currentTimeMillis();
System.out.println(name + "考试完毕");
long time = end - start;
return time;
}
public String getName() {
return name;
}
}
public class TestRoom {
ExecutorService testPool;
Map<Student, Future<Long>> stuToTimeMap;
public TestRoom() {
testPool = Executors.newFixedThreadPool(4);
stuToTimeMap = new HashMap<>();
}
public void test(Student student){
Future<Long> result = testPool.submit(student);
stuToTimeMap.put(student, result);
}
public Long getTime(Student student){
Future<Long> result = stuToTimeMap.get(student);
try {
return result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
public void endTest(){
if(!testPool.isShutdown()){
testPool.shutdown();
}
}
}
public class Test {
public static void main(String[] args) {
int stuNum = 8;
List<Student> stuList = new ArrayList<>();
TestRoom testRoom = new TestRoom();
for (int i = 0; i < stuNum; i++){
Student student = new Student("考生" + (i+1));
testRoom.test(student);
stuList.add(student);
}
for(Student stu : stuList){
System.out.println(stu.getName() + "考试用时为:"+ testRoom.getTime(stu)+"毫秒");
}
testRoom.endTest();
}
}
延迟执行、周期执行的执行器
如果要在某一时间之后执行线程操作,或者周期性地重复执行线程操作,则可以使用工厂类Executors的方法newScheduledThreadPool或方法newSingleThreadExecutor。方法newScheduledThreadPool使用给定数目的线程来调度执行任务,而方法newSingleThreadExecutor在一个单独的线程中调度执行任务。这两个方法都将返回一个ScheduledExecutorService线程池对象。
接口ScheduledExecutorService
接口ScheduledExecutorService从接口ExecutorService继承而来,可用于在给定的延迟后运行某个任务,或周期性的执行某个任务。
有关提交定时任务的四个方法:
//提交一个仅仅执行一次的任务
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
创建一个一性执行动作,该执行将在给定的时间延迟后启动
//提交一个仅仅执行一次的任务,有返回值
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
创建一个ScheduledFuture对象,该对象将在给定的延迟后调用callable
// 固定时间间隔循环的执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
创建并执行一个周期性执行的动作command,首次运行在一个给定的时间延迟initialDelay后启用,随后操作将随着周期period而周期执行
// 固定延时间隔的循环的执行
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
创建并执行一个周期性执行的动作command,首次运行在一个给定的时间延迟initialDelay后启用,随后操作将在该次结束和下次开始之间有一个固定的延迟delay
接口ScheduledFuture
接口ScheduledExecutorService的4个方法都将返回ScheduledFuture对象。ScheduledFuture也是一个接口 ,他从接口Future和Delayed继承而来,表示一个延迟的、结果可接收的操作。
public interface ScheduledFuture<V> extends Delayed, Future<V> {
}
下面通过几个例子演示上面介绍的几个类和接口的使用方法。
监控某一个设备的工作温度,当温度超多10后,没隔一秒钟发出一次报警提示,如果联连续发出10次报警后,仍没有被处理,则停止设备的运行。
通过线程模拟实际问题。设置两个线程 ,一个线程表示设备,设备在运行的过程中,温度将不断升高;另一个线程表示一个监视系统,用于监控设备的运行情况。在主程序中采用方法scheduleAtFixedRate调度这两个线程,当设备温度超过10时发出警告,当警告超过10次时,采用方法cancel或者shutdown关闭设备。
代码示例:
public class Machine implements Runnable {
private int temperature;
public Machine(int temperature) {
this.temperature = temperature;
}
@Override
public void run() {
perform();
temperature++;
System.out.println("机器的工作温度在升高,当前温度:" + temperature);
}
private void perform(){
int temp = (int) (Math.random() * Integer.MAX_VALUE);
int sum = 0;
for(int i = 0; i < temp; i++)
sum +=i;
}
public int getTemperature() {
return temperature;
}
}
public class Monitor implements Runnable {
private Machine machine;
ScheduledExecutorService scheduledExecutorService;
static int n = 0;
public Monitor(Machine machine, ScheduledExecutorService scheduledExecutorService) {
this.machine = machine;
this.scheduledExecutorService = scheduledExecutorService;
}
@Override
public void run() {
if (machine.getTemperature() >= 10) {
System.out.println("警告:机器温度过高");
n++;
}
if (n > 10) {
System.out.println("提醒次数限制已到,终止任务");
scheduledExecutorService.shutdown();
}
}
}
public class Test {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
Machine machine = new Machine(0);
Monitor monitor = new Monitor(machine, scheduledExecutorService);
scheduledExecutorService.scheduleAtFixedRate(machine, 1,2, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(monitor,0,1,TimeUnit.SECONDS);
}
}
机器的工作温度在升高,当前温度:1
机器的工作温度在升高,当前温度:2
机器的工作温度在升高,当前温度:3
机器的工作温度在升高,当前温度:4
机器的工作温度在升高,当前温度:5
机器的工作温度在升高,当前温度:6
机器的工作温度在升高,当前温度:7
机器的工作温度在升高,当前温度:8
机器的工作温度在升高,当前温度:9
机器的工作温度在升高,当前温度:10
警告:机器温度过高
警告:机器温度过高
机器的工作温度在升高,当前温度:11
警告:机器温度过高
警告:机器温度过高
机器的工作温度在升高,当前温度:12
警告:机器温度过高
警告:机器温度过高
机器的工作温度在升高,当前温度:13
警告:机器温度过高
警告:机器温度过高
机器的工作温度在升高,当前温度:14
警告:机器温度过高
警告:机器温度过高
机器的工作温度在升高,当前温度:15
警告:机器温度过高
提醒次数限制已到,终止任务
scheduleAtFixedRate定时任务超时问题
设置任务间隔时间 60s
第一个任务 80s ;第二个任务 20s 第三个任务 50s
第一个任务 0 —80s
第二个任务 80s—100s(上次任务已超时,本次不会再等待60s,会马上开始),
第三个任务 140s-190s
第四个任务 200s开始