任务执行:
- 大多数并发应用程序都是围绕"任务执行"来构造的:任务通常是一些抽象的且离散的工作单元。
在线程中执行任务:
- 理想情况下,各个任务之间是相互独立的:任务并不依赖其他任务的状态,结果或边界效应。
串行地执行任务:
/**
* 串行处理请求:
* 简单正确,但性能低下
*/
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 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();
}
...
}
/**
* 为每一个用户请求创建一个线程为其服务
*/
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();
}
...
}
/**
* 基于线程池的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(...}
//创建固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,若有线程发生异常,则会重新创建
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;
...
}
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
}
}
/**
* 对线程池进行生命周期管理
*/
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。
- 绝对时间,ScheduledThreadPoolExecutor基于相对时间。
- 只能创建一个线程,若某个任务执行时间过长,容易破坏其他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");
}
}
}
/**
* 错误的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);
}
}
...
}
/**
* 串行地渲染页面元素, 性能很低下
* 下载图片过程中有可能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个生命周期:创建,提交,开始,完成。
- 已提交但未开始执行的任务,对于已经开始执行的任务,只能当他们能响应中断时,才能取消,取消已经完成的任务不会有影响。
/**
* 使用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
}
}
...
}
/**
* 使用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()
}
}
...
}
/**
* 使用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;
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;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit) throws InterruptedException;
不吝指正。