学习书籍推荐《Java Concurrency In Practice》
在大多数服务器应用程序中都存在一个明显的任务边界: 单个客户请求.
但是有时候,任务边界并非是显而易见的,例如在很多桌面应用程序中,即使是服务器应用程序,在单个客户请求中,仍然可能存在可发掘的并行性,例如数据库服务器.
非常糟糕的Timer行为
一个异常将导致后面所有的任务,包括与该任务无关的任务都无法执行
import java.util.Timer;
import java.util.TimerTask;
public class OutOfTime {
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
timer.schedule(new ThrowTask(), 1);
Thread.sleep(1);
timer.schedule(new ThrowTask(), 1);
Thread.sleep(500);
}
static class ThrowTask extends TimerTask{
@Override
public void run() {
throw new RuntimeException();
}
}
}
import com.sun.scenario.effect.ImageData;
public class ImageInfo{
public ImageData downloadImage() {
return null;
}
}
串行HTML页面渲染器
先绘制文本元素,同时为图像预留出矩形的站位空间,在处理完第一遍文本后,程序再开始下载图像,并将它们绘制到相应的占位空间中.
import java.util.ArrayList;
import java.util.List;
import com.sun.scenario.effect.ImageData;
public class SingleThreadRenderer {
void renderPage(CharSequence source){
renderText(source);
List<ImageData> imageData = new ArrayList<ImageData>();
for(ImageInfo imageInfo: scanForImageInfo(source)){
imageData.add(imageInfo.downloadImage());
}
for(ImageData data : imageData){
renderImage(data);
}
}
private void renderImage(ImageData data) {
}
private List<ImageInfo> scanForImageInfo(CharSequence source) {
List <ImageInfo> list = new ArrayList<ImageInfo>();
/**
* 转换操作
*/
return list;
}
private void renderText(CharSequence source) {
}
}
图像下载过程的大部分时间都是在等待I/O操作执行完成,在这期间CPU几乎不做任何工作,因此,这种串行执行方法没有充分地利用CPU,使得用户在看到最终页面之前要等待过长的时间.通过将问题分解为多个独立的任务并发执行,能够获得更高的CPU利用率和响应灵敏度.
使用Future实现页面渲染器
Future有助于表示这些协同任务之间的交互
public class FutureRenderer {
private final ExecutorService executor = Executors.newFixedThreadPool(30);
void renderPage(CharSequence source){
final List<ImageInfo> imageInfos = scanForImageInfo(source);
Callable<List<ImageData>> task = new Callable<List<ImageData>>(){
@Override
public List<ImageData> call() throws Exception {
List<ImageData> result = new ArrayList<ImageData>();
for(ImageInfo imageInfo: imageInfos){
result.add(imageInfo.downloadImage());
}
return result;
}
};
Future <List<ImageData>> future = executor.submit(task);
renderText(source);
try {
List<ImageData> imageData = future.get();
for(ImageData data : imageData){
renderImage(data);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
future.cancel(true);
} catch (ExecutionException e) {
try {
throw landerThrowable(e.getCause());
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
private Exception landerThrowable(Throwable cause) {
return null;
}
private void renderImage(ImageData data) {
// TODO Auto-generated method stub
}
private void renderText(CharSequence source) {
// TODO Auto-generated method stub
}
private List<ImageInfo> scanForImageInfo(CharSequence source) {
List <ImageInfo> list = new ArrayList<ImageInfo>();
/**
* 转换操作
*/
return list;
}
}
FutureRenderer使用了两个任务,其中一个负责渲染文本,另一个负责下载图像,如果渲染文本的速度远远高于下载图像速度,那么程序的最终性能与串行执行时的性能差别不大,而代码却变得更复杂了.
使用CompletionService实现页面渲染
如果向Executor提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务关联的Future,然后反复使用get方法,同时将参数timeout指定为0,从而通过轮询来判断任务是否完成.这种方法虽然可行,但却有些繁琐,幸运的是,还有一种更好的方法,通过CompletionService来完成服务.
CompletionService将Executor和BlockingQueue的功能融合在一起.
void renderPage(CharSequence source){
final List<ImageInfo> info = scanForImageInfo(source);
CompletionService<ImageData> completionService = new ExecutorCompletionService<ImageData>(executor);
for(final ImageInfo imageInfo: info){
completionService.submit(new Callable<ImageData>(){
public ImageData call() throws Exception {
return imageInfo.downloadImage();
}
});
}
renderText(source);
try {
for(int t = 0 , n = info.size(); t< n;t++){
Future<ImageData> f = completionService.take();
ImageData imageData = f.get();
renderImage(imageData);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
try {
throw landerThrowable(e.getCause());
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
为任务设置时限
有时候,如果某个任务无法在指定时间内完成,那么将不再需要它的结果,此时可以放弃这个任务.
在使用显示任务时需要注意,当这些任务超时后应该立即停止,从而避免为继续计算一个不再使用的结果而浪费计算资源.
在指定时间内获取广告信息:
Page renderPageWithAd() throws InterruptedException{
long endNanos = System.nanoTime() + TIME_BUDGET;
Future<Ad> f = exec.submit(new FetchAdTask());
//在等待广告的同时显示页面
Page page = renderPageBody();
Ad ad;
try{
long timeLeft = endNanos - System.nanoTime();
ad = f.get(timeLeft,NANOSECONDS);
}catch(ExecutoinException e){
ad = DEFAULT_AD;
}catch(TimeoutExecption e){
ad = DEFAULT_AD;
f.cancel(true);
}
page.setAd(ad);
return page;
}
示例: 旅行预订门户网站
“预定时间” 方法可以很容易地扩展到任意数量的任务上,考虑这样一个旅行预订门户网站: 用户输入旅行日期的时间和其他要求,门户网站获取并显示来自多条航线,旅店或汽车租赁公司的报价.在获取不同公司报价过程中,可能会调用web服务,访问数据库,执行一个EDI事务或其他机制.在这种情况下,不宜让页面的响应时间首先于最慢的响应时间,而应该只显示在指定时间内收到的信息.
在预定时间内请求旅游报价:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class Travel {
private final ExecutorService exec = Executors.newFixedThreadPool(30);
public List<TravelQuote> getRankedTravelQuotes(
TravelInfo travelInfo, Set<TravelCompany> companies,
Comparator<TravelQuote> ranking,long time,TimeUnit unit) throws InterruptedException{
//任务集合
List<QuoteTask> tasks = new ArrayList<QuoteTask>();
for(TravelCompany company : companies){
tasks.add(new QuoteTask(company, travelInfo));
}
//线程集合
List<Future<TravelQuote>> futures = exec.invokeAll(tasks,time,unit);
//存储报价信息
List<TravelQuote> quotes = new ArrayList<TravelQuote>(tasks.size());
//任务迭代器
Iterator<QuoteTask> taskIter = tasks.iterator();
for (Future<TravelQuote> f : futures) {
//获取当前线程对应的执行的任务
QuoteTask task = taskIter.next();
try{
quotes.add(f.get());
}catch (ExecutionException e) {
quotes.add(task.getFailureQuote(e.getCause()));
}catch (CancellationException e) {
quotes.add(task.getTimeoutQuote(e));
}
}
Collections.sort(quotes,ranking);
return quotes;
}
}
class QuoteTask implements Callable<TravelQuote>{
private final TravelCompany company;
private final TravelInfo travelInfo;
public QuoteTask(TravelCompany company,TravelInfo travelInfo) {
this.company = company;
this.travelInfo = travelInfo;
}
public TravelQuote getTimeoutQuote(CancellationException e) {
// TODO Auto-generated method stub
return null;
}
public TravelQuote getFailureQuote(Throwable cause) {
// TODO Auto-generated method stub
return null;
}
public TravelQuote call() throws Exception{
return company.solicitQuote(travelInfo);
}
}
class TravelQuote{
}
class TravelCompany{
public TravelQuote solicitQuote(TravelInfo traveInfo) {
return null;
}
}
class TravelInfo{
}