简介
FutureTask
:一个可取消的异步计算。FutureTask
提供了对Future的基本实现,可以使用FutureTask
异步执行任务,调用其get
方法获取返回结果。
本章目标
- 演示一般情况下分页查询用户的代码。
- 使用
FutureTask
优化分页查询用户的代码,提高效率。
分页查询用户案例
创建UserService类
- 通常分页查询数据,会分成以下2部分。
- (1)查询数据总条数。
- (2)分页查询数据,sql使用offset,limit参数。
- 下面分成2个方法(总条数、分页数据),为了模拟查询数据的效果,固定要耗时2毫秒。代码如下所示。
private int userCount() {
try {
Thread.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 10;
}
private List userData(int currentPage) {
try {
Thread.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new ArrayList<>();
}
public Object findUserPageList() {
long startTime = System.currentTimeMillis();
int userCount = userCount();
System.out.println(userCount);
List list = userData(1);
System.out.println(list);
long endTime = System.currentTimeMillis();
long diffTime = endTime - startTime;
System.out.println("总共耗时:" + diffTime);
return null;
}
创建UserController类
- 声明controller层调用service层方法,代码如下所示。
@RestController
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/findUserPageList")
public Object findUserPageList(){
return userService.findUserPageList();
}
}
查询效果
- 调用/findUserPageList接口3次效果如下图所示。
使用FutureTask优化业务代码
- 从上图可以看出,最长的竟然长达6毫秒,那么如何优化呢?
- 答案就是
异步
,但是普通的线程是不行的,因为不能接收到返回值。 - JUC包里就提供了
FutureTask
,供开发者使用。 - 下面开启
FutureTask
之旅,只需要改动UserService#findUserPageList
方法就可以了。
修改UserService代码
- 创建
FutureTask
对象,构造器中传入Callable
对象,使用子线程执行
,最后调用get
方法阻塞得到返回值。代码如下所示。
public Object findUserPageList() {
long startTime = System.currentTimeMillis();
Callable userCountFutureCallable = new Callable() {
@Override
public Integer call() throws Exception {
return userCount();
}
};
FutureTask<Integer> userCountFuture = new FutureTask(userCountFutureCallable);
Callable userDataFutureCallable = new Callable() {
@Override
public List call() throws Exception {
return userData(1);
}
};
FutureTask<List> userDataFuture = new FutureTask(userDataFutureCallable);
new Thread(userCountFuture).start();
new Thread(userDataFuture).start();
Integer userCount = 0;
try {
userCount = userCountFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(userCount);
List list = null;
try {
list = userDataFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(list);
long endTime = System.currentTimeMillis();
long diffTime = endTime - startTime;
System.out.println("总共耗时:" + diffTime+"毫秒");
return null;
}
修改后的效果
- 依旧是调用/findUserPageList接口3次,结果如下图所示。
- 从上图可以看出,效率大大提升了。
将FutureTask交给线程池执行
- 其实还有待优化的地方,例如现在是每调用一次就创建子线程。我们都知道,
不可能无限创建子线程的,并且线程的创建和销毁也有很大的损耗
。这个时候可以使用线程池,代码如下所示。
private final int processors = Runtime.getRuntime().availableProcessors();
private final ExecutorService executorService = new ThreadPoolExecutor(processors * 2, processors * 10, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(processors * 100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
public Object findUserPageList() {
...省略...
executorService.execute(userCountFuture);
executorService.execute(userDataFuture);
...省略...
return null;
}