线程池

来源:互联网 发布:ae cs6 mac汉化破解版 编辑:程序博客网 时间:2024/06/12 07:21

线程池是指管理一组同结构工作线程的资源池。“在线程池中执行任务”比“为每个任务分配一个线程”优势更多。通过重用现有的线程而不是创建新的线程,可以在处理多个请求时分摊在下线程出安静啊和销毁线程过程中产生的巨大开销。另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会等待创建线程而延迟任务的执行,从而提供响应性。通过适当调成线程池的大小,可以创建足够多的线程以便使处理器保持忙碌的状态,同时还可以防止过多线程相互竞争资源而使应用程序 耗尽内存或失败。

类库提供了一个灵活的线程池以及一些有用的默认配置。可以通过Executors中的静态工厂方法之一来创建线程池。‘’

newFixedThreadPool:创建一个固定长度的线程池,每当提交一个任务时将创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不在变化(如果某个线程由于发生了未逾期的Exception而结束,那么线程池会补充一个新的线程)。

newCachedThreadPool:创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。

newSingleThreadExecutor:是一个单线程的Executor是一个单线程的Executor,它创建了单个工作线程来执行任务,如果这个线程异常结束,会创建另一个线程来替代。nexSingleThreadExecutor能确保依照任务在队列中的顺序来执行(FIFO、LIFO、优先级)。

newScheduledThreadPool:创建一个固定长度的线程池,而且以延时或定时的方式来执行任务。

1. Executor的生命周期

public interface ExecutorService implements Executor{    void shutdown();    List<Runnable> shutdownNow();    boolean isShutDown();    boolean isTerminated();    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;} 

ExecutorService的生命周期有3种状态:运行、关闭和已终止。ExecutorService在初始创建时处于运行状态。shutdown方法将执行平缓的关闭过程:不再接受新的任务,同时等待已提交的任务执行完成——包括哪些还未开始执行的任务。shutdownNow方法将执行粗暴的关闭过程:它将尝试取消所有运行中的任务,并且部再启动队列中尚未开始执行的任务。

在ExecutorService关闭后提交的任务将由“拒绝执行处理器(Rejected Exception Handler)”来处理,它会抛弃任务,或者是的execute方法抛出一个未检查的RejectedExecutionException。等待所有任务完成后,ExecutorService将进入终止状态。可以调用awaitTermination来等待ExecutorService到达终止状态,或者通过调用isTerminated来轮询ExecutorService是否已经终止。通常在调用awaitTermination之后会立即调用shutdown,从而产生同步地关闭ExecutorService的效果。

2. 携带结果的任务Callable与Future

Runnable和Callable描述的都是抽象的计算任务。这些任务通常是由范围的,即都有一个明确的起始点,并且最终会结束。Executor执行的任务有4个生命周期截断:创建、提交、开始和完成。由于有些任务可能要执行很长的时间,因此通常希望能够取消这些任务,在Executor框架中,已经提交但尚未开始的任务可以取消,但对于那些已经开始的任务,只有当他们能响应中断时,才能取消。取消一个已完成的任务不会有任何影响。

Future表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。

get方法的行为取决于任务的状态(尚未开始、正在运行、已完成)。如果任务已完成,那么get会立即返回或者抛出一个Exception。如果任务没有完成,那么get将阻塞并直到任务完成,如果任务抛出异常,那么get将异常封装为ExcetorException并重新抛出。如果任务被取消,那么get将抛出CancellationException。如果get抛出了ExecutorException,那么可以通过getCause来获得被封装的初始异常。

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

3. CompletionService:Executor与BlockingQueue

如果向Executor提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务关联的Future。然后反复使用get方法,同时将参数timeout指定为0,从而来判断任务是否完成。这种方法虽然可行,但却有些繁琐。CompletionService将Executor和BlockQueue的功能融合在一起,可以将Callable任务提交给它来执行,然后使用类似于队列操作的take和poll等犯法来获得已完成的记过,这些结果会在完成时被封装为Future。ExecutorCompletionService实现了CompletionService并将计算部分委托给Executor。

示例:使用CompletionServcie实现页面渲染器

可以通过CompletionService从两个方面来提高页面渲染器的性能:缩短总运行时间以及提高响应性。为每一幅图像的下载都创建一个独立任务,并在线程池中执行它们,从而将串行的下载过程转换为并行的过程:这将减少下载所有图像的总时间,此外,通过从CompletionService中获取结果以及使每张图片在下载完成后立刻显示出来,能使用户获得一个更加动态和更高相应性的用户界面。如下列程序清单所示。

public class Render{    private final ExecutorSerivce executor;    Render(ExecutorService executor){this.executor = executor}    void renderPage(CharSequence source){        List<ImageInfo> info = scanForImageInfo(source);        CompletionService<ImageData> completionService =             new ExecutorCompletionService<ImageInfo>(executor);        for(final ImageInfo imageInfo : info){            completionService.submit(new Callable<ImageData(){                public ImageData call(){                    return imageInfo.downloadImage();                }            });        }        renderText(source);        try{            for(int t = 0; t < info.size(); t++){                Future<ImageData> f = completionService.take();                ImageData imageData = f.get();                renderImage(imageData);            }        }catch (InterruptException e){            Thread.currentThread().interrupt();        }catche (ExecutionException e){            throw lanuderThrowable(e.getCause);        }    }}

4. 为任务设置时限

夏磊的程序清单给出了限时Future.get的一组典型应用。在它生成的叶念中包括响应用户请求的内容以及从广告服务器上获得的广告,它将获取广告的任务提交给一个Executor,然后计算剩余的文本页面内容,最后等待广告信息,直到超出指定的时间,如果get超时,那么取消广告获取任务,并转而使用默认的广告信息。

Page renderPageWithAd() throws InterrupedException{    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, NANOSSECONDS);    }catch (ExecutionException e){        ad = DEFAULT_AD;    }catch (TimeoutException e){        ad = DEFAULT_AD;        f.cancel(true);    }    page.setAd(ad);    return page;}

5. 扩展ThreadPoolExecutor

在下列的程序清单给出了一个自定义的线程池,它通过beforeExecute、afterExecute和terminated等方法来添加日志记录和统计信息收集,为了测量任务的执行时间,beforeExecute必须记录开始时间并把它保存到一个afterExecute可以方法的地方。因为这些地方将在执行任务的线程中调用,因此beforeExecute可以把值保存到一个ThreadLocal变量中,然后由afterExecute来读取。在TimingTheadPool中使用了两个atomicLong变量,用于记录已处理的任务数和总的处理时间,并通过terminated来输出包含平均任务执行时间的日志消息。

public class TimingThreadPool extends ThreadPoolExecutor{    private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();    private final Logger log = Logger.getAnonymousLogger();    private final AtomicLong numTasks = new AtomicLong();    private final AtomicLong totalTime = new AtomicLong();    public void beforeExecute(Thread t, Runnable r){        super.beforeExecute(t, r);        log.fine(String.format("Thread %s: start %s", t, r));        startTime.set(System.nanoTime());    }    public void afterExecute(Runnable r, Throwable t){        try{            long endTime = System.nanoTime();            long taskTime = endTime - startTime.get();            numTasks.incrementAndGet();            totalTime.addAndGet(taskTime);            log.fine(String.format("Thread %s: end %s, time=%dns", t, r, taskTime));        }finally{            super.afterExecute(r, t);        }    }    public void terminated(){        try{            log.info(String.format("Terminated: avg time=%dns", totalTime.get()/numTasks.get()));        }finally{            super.terminated();        }    }

6. 饱和策略

当有界队列被填满后,饱和策略开始发挥作用。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。JDK提供了几种不同的RejectedExecutionHandler实现,每种实现包含不同的饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。

“中止(Abort)”策略是默认的饱和策略,该策略将抛出未检查的RejectExecution-Exception。调用者可以捕获这个异常,然后根据自己需求编写自己的处理代码。

“抛弃(DisCard)”策略是当新提交的任务无法保存到队列中等待执行时,策略会悄悄的放弃该任务。“抛弃最旧的(DisCard-Oldest)”策略则会抛弃下一个将被执行的任务,然后尝试重新提交新的任务(如果工作队列是一个优先队列,那么“抛弃最旧的”将会抛弃优先级最高的任务,因此最好不要将“抛弃最旧的”饱和策略和优先级队列放在一起使用)

“调用者运行(Caller-Runs)”策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。它不会在线程池的某个先吃中执行新提交的任务,而是在一个调用execute的线程中执行该任务。由于执行任务需要一定的时间,因此线程在一段时间内部能提交新的任务。

下列的程序清单给出了如何创建一个固定大小的线程池,同时使用“调用者运行”饱和策略

TheadPoolExecutor executor = new ThreadPoolExecutor(N_THREADS, N_THREADS, 0L,                                                    TimeUnit.MILLISECONDS,                                                    new LinkedBlockingQueue<Runnable>(CAPACITY));executor.setRejectedExecutionHandler( new ThreadPoolExecutor.CallerRunsPolicy());
原创粉丝点击