java并发编程(五): 任务执行

任务执行:

  • 大多数并发应用程序都是围绕"任务执行"来构造的:任务通常是一些抽象的离散的工作单元。

在线程中执行任务:

  • 理想情况下,各个任务之间是相互独立的:任务并不依赖其他任务的状态结果边界效应

串行地执行任务:

/**
 * 串行处理请求:
 * 简单正确,但性能低下
 */
public class SingleThreadWebServer {
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(80);
		boolean listening = true;
		while (listening){
			Socket connection = server.accept(); //阻塞等待客户端连接请求
			handlerRequest(connection);
		}
		server.close();
	}
       ...
}

显示地为任务创建线程:

/**
 * 为每一个用户请求创建一个线程为其服务
 */
public class ThreadPerTaskWebServer {
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(80);
		boolean listening = true;
		while (listening){
			final Socket connection = server.accept(); //阻塞等待客户端连接请求
			Runnable task = new Runnable() {
				@Override
				public void run() {
					handlerRequest(connection);
				}
			};
			new Thread(task).start();
		}
		server.close();
	}
       ...
}
上面的实现至少能给我们一些暗示:
  • 任务处理过程从主线程分离出来,以提高主线程响应其他请求的能力。
  • 任务可以并行处理,提高吞吐量。
  • 任务处理代码必须线程安全

但这样无限制创建会带来不足:

  • 线程生命周期的开销非常高。线程的创建和销毁都是有代价的,不同平台开销也不同。
  • 资源消耗。太多线程会消耗系统资源,如空闲线程的内存占用,大量线程竞争CPU时产生其他性能开销等。
  • 稳定性。可创建线程数会受到限制,如jvm启动参数(如-Xss等),Thread构造函数请求的栈大小,以及底层操作系统对线程的限制(32位机器上,主要限制因素为线程栈的寻址空间)等,破坏这些限制,很可能抛出OutOfMemoryError异常。

Executor框架:

  • 任务是一组逻辑工作单元,而线程则是使任务异步执行的机制
  • java类库中,任务执行的抽象不是Thread, 而是Executor
/**
 * 基于线程池的Web服务器
 */
public class ThreadPerTaskWebServer {
	private static final int NTHREADS = 100;
	/**
	 * 创建固定线程数量的线程池
	 */
	private static final Executor exec = 
			Executors.newFixedThreadPool(NTHREADS);
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(80);
		boolean listening = true;
		while (listening){
			final Socket connection = server.accept(); //阻塞等待客户端连接请求
			Runnable task = new Runnable() {
				@Override
				public void run() {
					handlerRequest(connection);
				}
			};
			exec.execute(task);
		}
		server.close();
	}
       ...
}

执行策略:

执行策略需要考虑的有:

  • 什么线程中执行任务。
  • 任务按照什么顺序执行(FIFO, LIFO, 优先级)。
  • 多少个任务能并发执行。
  • 在队列中有多少个任务在等待执行。
  • 由于过载,系统应如何拒绝任务?如果通知任务被拒绝
  • 执行任务前后,应该做什么?

线程池:

  • 线程池:管理一组同构工作线程的资源池。

Executors提供了几种创建线程池的方法:

//创建固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,若有线程发生异常,则会重新创建
public static ExecutorService newFixedThreadPool(int nThreads) {...}
//创建单个线程来执行任务,若该线程发生异常,会创建一个新的线程。该池可按顺序执行队列中的任务(如FIFO,LIFO,优先级等)
public static ExecutorService newSingleThreadExecutor() {...}
//该线程池无长度限制,在线程过多时会回收,过少时会创建
public static ExecutorService newCachedThreadPool() {...}
//创建一个固定长度的线程池,并以延迟或定时的方式执行任务
public static ScheduledExecutorService newScheduledThreadPool(...}

Executor的生命周期:

  • 我们可以通过ExecutorService来对线程池进行生命周期的管理:
public interface ExecutorService extends Executor {

    void shutdown();//平缓关闭,不接受新任务,待提交的任务执行完毕后,再关闭
    List<Runnable> shutdownNow();//粗暴关闭,尝试取消所有执行中的任务,不再启动队列中尚未开始执行的任务
    boolean isShutdown(); //是否已关闭
    boolean isTerminated(); //是否已终止
    boolean awaitTermination(long timeout, TimeUnit unit)//等待ExecutorService到达终止状态
        throws InterruptedException;
    ...
}
  • ExecutorService生命周期状态:运行关闭已终止
/**
 * 对线程池进行生命周期管理
 */
public class LifecycleWebServer {
	private static final int NTHREADS = 100;
	private final ServerSocket server;
	
	public LifecycleWebServer() throws IOException{
		server = new ServerSocket(80);
	}
	
	/**
	 * 创建固定线程数量的线程池
	 */
	private static final ExecutorService exec = 
			Executors.newFixedThreadPool(NTHREADS);
	
	public void start() throws IOException{
		while (!exec.isShutdown()){
			try {
				final Socket connection = server.accept(); //阻塞等待客户端连接请求
				Runnable task = new Runnable() {
					@Override
					public void run() {
						handlerRequest(connection);
					}
				};
				exec.execute(task);
			} catch (RejectedExecutionException e) {
				if (!exec.isShutdown()){
					//task submission is rejected
				}
			}
		}
	}
	
	public void stap() throws IOException{
		exec.shutdown(); //平缓关闭线程池
		server.close();
	}

	private static void handlerRequest(Socket connection) {
		// handle request
	}
}

延迟任务与周期任务:

  • 建议通过ScheduledThreadPoolExecutor来代替Timer,TimerTask
  • Timer基于绝对时间,ScheduledThreadPoolExecutor基于相对时间
  • Timer执行所有定时任务只能创建一个线程,若某个任务执行时间过长,容易破坏其他TimerTask的定时精确性
  • 已经调度但未执行的TimerTask将不会再执行,新的任务也不会被调度,出现"线程泄漏",如:
/**
 * 错误的Timer行为,Timer是脆弱的
 */
public class OutOfTime {
	public static void main(String[] args) throws InterruptedException {
		Timer timer = new Timer();
		timer.schedule(new ThrowTask(), 1); //第一个任务抛出异常
		Thread.sleep(1000); 
		timer.schedule(new ThrowTask(), 1); //第二个任务将不能再执行, 并抛出异常Timer already cancelled.
		Thread.sleep(5000);
		System.out.println("end.");
	}
	
	static class ThrowTask extends TimerTask{

		@Override
		public void run() {
			throw new RuntimeException("test timer's error behaviour");
		}
	}
}

串行的页面渲染器:

/**
 * 串行地渲染页面元素, 性能很低下
 * 下载图片过程中有可能IO时间长阻塞,
 * CPU没能有效利用
 */
public class SingleThreadRenderer {
	void rendererPage(CharSequence source){
		renderText(source);
		List<ImageData> imageDatas = new ArrayList<>();
		//解析文本中的图片连接
		for (ImageInfo imageInfo : scanForImage(source)){ 
			imageDatas.add(imageInfo.downloadImageData()); //下载图片
		}
		//渲染图片
		for (ImageData data : imageDatas){
			renderImage(data);
		}
	}
       ...
}

携带结果的任务Callable与Future:

  • Executor执行任务的4个生命周期:创建提交开始完成
  • Executor框架中,可以取消已提交但未开始执行的任务,对于已经开始执行的任务,只能当他们能响应中断时,才能取消,取消已经完成的任务不会有影响。
/**
 * 使用Future等待图像下载
 * 将渲染过程分为:
 *     IO密集型(下载图像)
 *     CPU密集型(渲染页面)
 * 但这里仍然必须图片下载完成了才能看到页面,只是缩短了总时间
 */
public class FutureRenderer {
	private final ExecutorService exec = Executors.newFixedThreadPool(10);
	
	void rendererPage(CharSequence source){
		final List<ImageInfo> imageInfos = scanForImage(source); //抽出图片链接信息
		Callable<List<ImageData>> task = 
				new Callable<List<ImageData>>() {
					@Override
					public List<ImageData> call() throws Exception {
						List<ImageData> result = 
								new ArrayList<>();
						for (ImageInfo imageInfo : imageInfos){
							result.add(imageInfo.downloadImageData()); //下载图片
						}
						return result;
					}
				};
		Future<List<ImageData>> future = exec.submit(task); //提交下载图片的任务
		renderText(source); //渲染文本
		try {
			List<ImageData> imageDatas = future.get();//阻塞获取下载的图片
			for (ImageData data : imageDatas){ //渲染图片
				renderImage(data); 
			}
		} catch (InterruptedException e) {
			//重新设置线程的中断状态
			Thread.currentThread().interrupt();
			future.cancel(true);
		} catch (ExecutionException e) {
			// handle exception
		} 
	}
        ...
}

异构任务并行化中存在的局限:

  • 当异构任务之间的执行效率悬殊很大时,对于整体的性能提升来看并不是很有效。比如要是上面下载图片过程时间比渲染文本时间长很多,那么整体并发提升并不是很明显。

CompletionService: Executor与BlockingQueue:

/**
 * 使用CompletionService, 使页面元素在下载完成后立即显示出来
 * 类似Mobile中的新闻加载,图片时被异步加载的
 */
public class Renderer {
	private final ExecutorService executor;
	
	public Renderer(ExecutorService executor){
		this.executor = executor;
	}
	
	void rendererPage(CharSequence source){
		final List<ImageInfo> imageInfos = scanForImage(source); //抽出图片链接信息
		CompletionService<ImageData> completionService
					= new ExecutorCompletionService<>(this.executor);
		for (final ImageInfo imageInfo : imageInfos){
			//提交下载图片的任务, 每下载一个图片就是一个任务,达到下载图片并行性
			completionService.submit(new Callable<ImageData>() { //内部会将执行完后封装的Future对象放到一个BlockingQueue中
				@Override
				public ImageData call() throws Exception {
					return imageInfo.downloadImageData();
				}
			});
		}
		renderText(source); //渲染文本
		try {
			for (int i=0, n=imageInfos.size(); i<n; i++){
				Future<ImageData> f = completionService.take();
				ImageData imageData = f.get();
				renderImage(imageData); //渲染图片
			}
		} catch (InterruptedException e) {
			//重新设置线程的中断状态
			Thread.currentThread().interrupt();
		} catch (ExecutionException e) {
			// handle exception e.getCause()
		} 
	}
       ...
}

为任务设置超时:

任务超时设置可通过Future的get超时版本:

V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
你可以捕获其TimeoutException来做相应处理即可。

提交多个任务:

可以通过ExecutorService提交一组任务:

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit) throws InterruptedException;
不吝指正。

转载于:https://my.oschina.net/indestiny/blog/213695

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值