3.4使用异步任务执行—Future
Executor 任务执行框架不仅支持线程池的建立和管理,而且为描述任务和执行提供了通用方式。
Executor 框架以Runnable 作为其接受的任务的基本描述形式。但诸如完成数据库查询、获取网络资源、进行复杂计算等工作的任务都会引起延迟,并需要返回结果,而Runnable 不支持结果的返回,因此JSR-166提供了Callable 接口:
public interface Callable{
V call() throws Exception
}
该接口的实现者必须实现一个不带任何参数并返回V 类型值的call 方法。进一步,JSR-166的Future 接口抽象异步计算结果的获取,提供判定计算是否完成的isDone方法,其get 方法负责等待计算的完成并获取结果。
在这些接口的基础上,Java 并发包中的FutureTask 类提供了对Future 接口的基本实现,仅在计算完成时才能获取结果,如果计算尚未完成,则自动阻塞get 方法。因为FutureTask 也实现了Runnable 接口,所以可使用FutureTask 包装Callable 或Runnable 对象,并将FutureTask 提交给执行框架去异步执行,再用get 方法等待任务的完成。
下面这段代码演示了Callable和FutureTask的使用:
Callable func = new Callable(){
public Integer call() throws Exception { // 实现call方法,执行异步任务
System.out.println("inside callable");
Thread.sleep(1000);
return new Integer(8);
}
};
FutureTask futureTask = new FutureTask(func);
Thread newThread = new Thread(futureTask); //启动线程执行异步任务
newThread.start();
try {
System.out.println("blocking here");
Integer result = futureTask.get(); //检查异步任务的返回结果
System.out.println(result);
} catch (InterruptedException ignored) {
} catch (ExecutionException ignored) {
}
3.5 尽可能减小锁的粒度
粗粒度的全局锁在保证线程安全的同时,也会损害应用的性能。仔细考虑锁的粒度在构建高可扩展 Java 应用时非常重要。当 CPU 个数和线程数较少时,全局锁并不会引起激烈的竞争,因此获得一个锁的代价很小(JVM 对这种情况进行了优化)。随着 CPU 个数和线程数增多,对全局锁的竞争越来越激烈。除了一个获得锁的 CPU 可以继续工作外,其他试图获得该锁的 CPU 都只能闲置等待,导致整个系统的 CPU 利用率过低,系统性能不能得到充分利用。当我们遇到一个竞争激烈的全局锁时,可以尝试将锁划分为多个细粒度锁,每一个细粒度锁保护一部分共享资源。通过减小锁的粒度,可以降低该锁的竞争程度。