结构化并发应用程序

195 阅读8分钟

任务执行

应用程序大多围绕“任务执行”构造

  • 任务通常是一些抽象且离散的工作单元
  • 应用程序的工作分解到多个任务中简化程序的组织结构
  • 提供一种自然的事物边界来优化错误恢复过程
  • 提供一种自然的并行工作结构来提升并发性

在线程中执行任务

1.找出清晰的任务边界 - 理想状况下每个线程相互独立,不依赖其他任务的状态、结构或边界效应 2. 明确任务的执行策略

  • 负载均衡 3.以独立的客户请求为边界。
  • 如:Web服务器、邮件服务器、文件服务器、EJB容器以及数据库服务器等通过网络接受客户的连接请求。
  • 将独立的请求作为任务边界,既可以实现任务的独立性,又可以实现合理的任务规模

小结

  • 任务处理过程从主线程中分离出来,使得主循环能够更快地重新等待下一个到来的连接。这使得程序在完成前面的请求之前可以接受新的请求,从而提高响应性。
  • 任务可以并行处理
  • 任务处理代码必须线程安全

无限制创建线程的不足

在生产环境中,“为每个任务分配一个线程”这种方法存在一些缺陷,尤其是当需要创建大量线程时:

  • 线程生命周期的开销非常高。线程创建和销毁都需要时间,延迟处理的请求并且需要JVM和操作系统提供一些辅助操作。会消耗大量的计算资源。
  • 资源消耗。活跃的线程会消耗系统资源,尤其是内存。
  • 稳定性。可创建线程的数量上存在一个限制,其影响因素包括JVM的启动参数、Thread构造函数中请求栈大小,以及底层操作系统对线程的限制等。破坏了限制很可能抛出OutOfMemoryError异常。

Executor框架

Executor接口:

public interface Executor {
    void execute (Runnable command);
}

使用示例

其基于生产者——消费者模式,如下是一个简单的基于ExecutorWeb服务器示例:

class TaskExecutionWebServer {
    private static final int NTHREADS = 100;
    private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocker(80);
        while(true) {
            final Socket connection = socket.accept();
            Runnable task = new Runnable() {
                public void run() {
                    handleRequest(connection);
                }
            };
            exec.execute(task);
        }
    }
}

通常Executor的配置是一次性的,因此在部署阶段可以完成,而提交任务的代码却会不断地扩散到整个程序中,增加了修改的难度。

为每一个请求启动一个新线程的Executor

public class ThreadPerTaskExecutor implements Executor {
    public void execute(Runnable r) {
        new Thread(r).start();
    };
}

在调用线程中以同步方式执行所有任务的Executor

public class WithinThreadExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}

执行策略

  • 在什么线程中执行任务?
  • 按照什么顺序? FIFO/LIFO/优先级
  • 多个并发任务
  • 队列的size
  • 过载的拒绝策略
  • 如何通知应用程序有任务被拒绝?
  • 执行前后有哪些动作?

每当看到new Thread(runnable).start()并希望获得一种灵活的执行策略时,考虑使用Executor来代替Thread

线程池

  • newFixedThreadPool 提交一个任务创建一个线程,直到到达线程池的最大数量,此时线程池规模不再变化。若某个线程发生异常而结束则补充一个新线程。
  • newCachedThreadPool 可缓存的线程池,超出规模则回收空闲线程,需求增加就新建新线程,规模没有任何限制
  • newSingleThreadPool 单线程的Executor
  • newScheduledThreadPool

使用Executor可以实现调优、管理、监视、记录日志、错误报告和其他功能,如果不使用任务执行框架,那么增加这些功能比较困难。

生命周期

运行、关闭和已终止

ExecutorService的生命周期管理办法:

public interface ExecutorService extends Executor {
    void shutdown();//不接收新任务
    List<Runnable> shutdownNow();//粗暴取消所有运行中任务
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    //其他用于任务提交的便利方法
}

ExecutorService关闭后提交的任务将由“拒绝执行处理器”,它会抛弃任务或者使得execute方法抛出一个未检查的RejectedExecutionException

支持关闭操作的Web服务器

class LifecycleWebServer {
    private final ExecutorService exec = ...;

    public void start() throw IOException {
        ServerSocket socket = new ServerSocket(80);
        while (!exec.isShutdown()) {
            try {
                final Socket conn = socket.accept();
                exec.execute(new Runnable() {
                    public void run() { handleRequest(conn);}
                });
            } catch (RejectedExecutionException e) {
                if(!exec.isShutdown())
                    log("task submission rejected", e);
            }
        }
    }

    public void stop() {exec.shutdown();}

    void handleRequest(Socket connection) {
        Request req = readRequext(connection);
        if(isShutdownRequest(req))
            stop();
        else
            dispatchRequest(req);
    }
}

找出可利用的并行性

如I/O操作时解放CPU,需要异步返回结果的线程。

CallableFuture接口

public interface Callable<V> {
    V call() throws Exception;
}

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException,
                CancellationException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
                CancellationException, TimeoutException;
}

ThreadPoolExecutor中的默认实现

protected <T> RunnableFuture<T> newTaskFor(Callable<T> task) {
    return new FutureTask<T>(task);
}

CompletonServiceBlockingQueue

CompletonServiceBlockingQueueExecutor融合

可以将Callable任务交给它,然后使用类似队列操作的takepoll等方法获得已完成结果,并将结果封装为Future

private class QueueingFuture<V> extends FutureTask<V> {
    QueueingFuture(Callable<V> c) {
        super(c);
    }
    QueueingFuture(Runnable t, V r) {
        super(t, r);
    }

    protected void done() {
        completionQueue.add(this);
    }
}

使用CompletonService实现页面渲染器

public class Renderer {
    private final ExecutorService executor;

    Renderer(ExecutorService executor) { this.executor = executor;}

    void renderPage(CharSequence source) {
        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() {
                    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) {
                throw launderThrowable(e.getCause());
            }
    }
}

为任务设置时限

Future.get超时的应用

  • 在指定时间内获取广告信息
Page rendeerPageWithAd() throw 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 (ExecutionException e) {
        ad = DEFAULT_AD;
    } catch (TimeoutException e) {
        ad = DEFAULT_AD;
        f.cancel(true);
    }
    page.setAd(ad);
    return page;
}
  • 旅行预订门户网站
private class QueueTask implements Callable<TravelQuote> {
    private final TravelCompany company;
    private final TravelInfo travelInfo;
    ...
    public TravelQueue call() throws Exception (
        TravelInfo travelInfo, Set<TravelCompany> companier,
        Comparator<TravelQueue> ranking, long time, TimeUnit unit)
        throws InterruptedException {
            List<QuoteTask> tasks = new ArrayList<QuoteTask>();
            for(TravelCompany company : companier)
                tasks.add(new QuoteTask(company, travelInfo));
            
            List<Future<TravelQuote>> futures = exec.invokeAll(tasks, time, unit);
            List<TravelQueue> quotes = new ArrayList<TravelQuote>(tasks.size());
            Interator<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;
        }
    }
}

任务取消

  • 用户请求取消
  • 有时间限制的请求
  • 应用程序事件
  • 错误
  • 关闭

利用volatile类型的域保存取消状态

@ThreadSafe
public class PrimeGenerator implements Runnable {
    @GuardedBy("this")
    private final List<BigInteger> primes = new ArrayList<BigInteger>();
    private volatile boolean cancelled;

    public void run() {
        BigInteger P = BigInteger.ONE;
        while (!canceled) {
            p = p.nextProbablePrime();
            synchronized(this) {
                primes.add(p);
            }
        }
    }

    public void cancel() {
        cancelled = true;
    }

    public synchronized List<BigInteger> get () {
        return new ArrayList<BigInteger>(primes);
    }
}
  • 关于中断 在JavaAPI或语言规范中,并没有将中断与任何取消语义关联起来,但实际上,如果在取消之外的其他操作中使用中断,那么都是不合适的,并且很难支撑起更大的应用。
    调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传传递了请求中的新消息。
    通常中断是实现取消最合理的方式。

通过中断来取消的示例:

class PrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;

    PrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted())
                queue.put(p = p.nextProbablePrime());
        } catch (InterruptedException consumed) {
            /**允许线程退出 */
        }
    }
    public void cancel() {interrupt();}
}

由于每个线程拥有各自的中断策略,因此除非你知道中断对该线程的意义,否则就不应该中断这个线程

批评者曾嘲笑Java的中断功能,因为它没有给出抢占式中断机制,并强迫开发人员必须处理InterruptedException。然而,通过推迟中断请求处理,开发人员能制定更灵活的中断策略,从而保持应用程序达到响应性和健壮性的平衡。

响应中断

  • 传递异常,从而使你的方法成为可中断的阻塞方法
  • 恢复中断状态,从而使用栈上的上层代码能够对其进行处理

只有实现了线程中断策略的代码才能屏蔽中断请求,在常规任务和库代码中都不应该屏蔽中断请求。

计时运行

  • join的不足:无法知道执行控制是因为线程正常退出而返回还是因为join超时而返回。
  • Future.get():抛出InterruptedExceptionTimeoutException时,如果不再需要结果就调用Future.cancel取消任务。

处理不可中断的阻塞

线程阻塞原因:

  • Java.io包中的同步Socket I/O 服务器应用程序中最常见的阻塞I/O形式就是套接字读取和写入,虽然InputStreamOutputStreamreadwrite等方法不会造成中断,但关闭底层的套接字可以使得readwrite等方法抛出一个SocketException
  • Java.io包中的同步I/O
    • 中断InterruptibleChannel上的等待线程,抛出ClosedByInterruptException并关闭链路
    • 关闭一个InterruptibleChannel将导致所有在链路操作上阻塞的线程都抛出AsynchronousCloseException
  • Selector的异步I/O
  • 获取某个锁

通过改写interrupt方法将非标准的取消操作封装在Thread

public class ReaderThread extends Thread {
    private final Socket socket;
    private final InputStream in;

    public ReaderThread(Socket socket) throws IOException {
        this.socket = socket;
        this.in = socket.getInputStream();
    }

    public void interrupt() {
        try {
            socket.close();
        } catch (IOException ignored) {

         } finally {
             super.interrupt();
         }
    }

    public void run() {
        try {
            byte[] buf = new byte[BUFSZ];
            while(true) {
                int count = in.read(buf);
                if(count < 0)
                    break;
                else if(count > 0)
                    processBuffer(buf, count);
            }
        } catch(IOException e){}
    }
}

通过newTaskFor将非标准的取消操作封装在一个任务中

public abstract class SocketUsingTask<T> implements CancellableTask<T> {
    @GuardedBy("this")
    private Socket socket;

    protected synchronized void setSocket(Socket s) {
        socket = s;
    }

    public synchronized void cancel() {
        try {
            if(socket != null)
                socket.close();
        } catch (IOException ignored) {

        }
    }

    public RunnableFuture<T> newTask() {
        return new FutureTask<T>(this) {
            public boolean cancel(boolean mayInterruptIfRunning) {
                try {
                    SocketUsingTask.this.cancel();
                } finally {
                    return super.cancel(mayInterruptIfRunning);
                }
            }
        };
    }
}