Java并发编程艺术 9 Java中的线程池

来源:互联网 发布:基础架构java 编辑:程序博客网 时间:2024/06/01 09:56
第9章  Java中的线程池

线程池的优点
【1】降低资源消耗。通过重复利用已创建的线程,可以减少创建、销毁线程的消耗
【2】提高响应速度。可以直接使用已创建线程。
【3】提高线程的管理性。通过线程池统一的分配、调优和监控。

线程池创建
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue) 最少参数
     {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
     }

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler)最多参数


int corePoolSize 线程池的基本的大小,当提交一个任务到线程池,线程池会创建一个线程来执行任务。即使有空闲线程也会创建新的线程。线程数到达corePoolSize大小时,就不在创建新的。如果使用线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
int maximumPoolSize 线程池最大数量,线程池允许创建的最大数量。如果任务队列已经满了,并且已创建的线程数小于maximumPoolSize,线程池会再创建新的线程。如果阻塞队列是无界队列,这个参数就没软用了。
long keepAliveTime 线程保持活动时间,工作线程空闲后保持的存活时间。
TimeUnit unit      线程保持活动时间的单位。可选DAYS天、HOURS小时、MINUTES分钟、MILLISECOUNDS毫秒
BlockingQueue<Runnable> workQueue  用于保存等待执行的任务的阻塞队列。可以选择ArrayBlockingQueue、LinkedBlockingQueue等
ThreadFactory threadFactory  用于设置创建线程的工厂,可以通过线程工厂为每个线程设置有意义的名字。使用了开源框架的guava提供的ThreadFactoryBuilder。
RejectedExecutionHandler handler  饱和策略。当队列和线程池饱和的情况下,采用的提交策略。默认是AbortPolicy



==========================================================================================================================




 ---------- 




提交任务:submit和execute

可以通过submit()和execute()方法提交任务。
区别:
【1】execute()是在Executor接口中定义,也是Executor中唯一的抽象方法。submit()是在ExecutorService中定义的(继承了Executor接口)
【2】execute()方法没有返回值,submit()返回一个Future对象。通过Future.get可以判断任务是否执行成功、或者执行完成。(主要区别)
【3】submit()方便主线程对Exception做处理。通过Future.get捕获抛出异常。
注意:Future.get方法是阻塞的。

void execute(Runnable command)  :
在将来某个时间执行给定任务。可以在新线程中或者在现有池线程中执行该任务。如果无法将任务提交执行,或者因为此执行程序已关闭,或者因为已达到其容量,则该任务由当前 RejectedExecutionHandler处理。
submit被重载三次
Future<?> submit(Runnable task)  提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功完成时将会返回null。如果抛出异常,get方法将捕获异常。
Future submit(Runnable task,T result)  Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功完成时将会返回给定的结果(也就是预先传递的result参数)。如果抛出异常,get方法将捕获异常。submit(task,null)和submit(task)是一样的。
Future submit(Callable task)  提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。该 Future 的 get 方法在成功完成时将会返回该任务的结果。 如果想立即阻塞任务的等待,则可以使用 result = exec.submit(aCallable).get(); 

public static void main(String[] args) throws InterruptedException, ExecutionException {
     BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
     ThreadPoolExecutor e= new ThreadPoolExecutor(20, 50, 1L, TimeUnit.MINUTES, workQueue);
//   Future<?> f = e.submit(new MyRunnable(),new Re());
     Future<String> f = e.submit(new MyCallable());
     System.out.println("future get : "+ f.get());
}

static class MyRunnable implements Runnable{
     public void run() {
           String name = "mm";
           System.out.println(name);
           throw new RuntimeException("haha");
     }
}
static class MyCallable implements Callable<String>{
     public String call() throws Exception {
           String name = "dd";
           System.out.println(name);
           Thread.sleep(5000);
           return name;
     }
}
static class Re{
     String re = "ok";
}
【1】execute方法性能较高,所以不需要返回值的时候尽量可以选用。
【2】如果需要返回值,可以实现Callable接口。如果只是需要得到空的结果使用submit就好。
【3】Callable和Future使用可以实现异步执行方法,异步获取执行结果。

关闭线程池
通过调用shutdown或者shutdownNow方法来关闭线程池。原理是遍历线程池中的工作线程,然后逐个调用interrupt方法来中断线程,所以无法响应中断的线程可能永远不能停止。
shutdownNow:将线程池状态设置为STOP,停止所有正在执行或空闲的线程,并返回等待执行任务的列表。
shutdown:只是将线程状态设置为SHUTDOWN,然后终端空闲的线程。

调用了以上的任意一个,isShutdown都会返回true。当所有任务都关闭以后才算线程池关闭成功,isTerminated方法才返回true。通常调用shutdown方法,如果任务不执行完也行,也可以使用shutdownNow。

合理配置线程池
1.判断任务性质:CPU密集型、IO密集型、混合型
2.可以选择使用优先级队列处理优先级不同的任务。(注意:可以导致低优先级的任务永远不执行)
3.建议使用有界队列。可以增加系统的稳定性和预警能力,维持在一定的任务数量,可以设置为几千。


线程池的监控
在系统中使用了大量线程池,有必要对线程池进行监控,可以根据线程池使用情况快速定位问题。
taskCount:线程需要执行的任务数量
completedTaskCount:线程池已完成的任务书,小于等于taskCount
largestPoolSize:线程池曾经创建的最大线程数。可以用来判断线程池是否满过。
getPoolSize:线程池的线程数量。线程池不销毁的话,线程池中的线程不会自动销毁,所以这个数值只增不减。

还可以对线程池进行扩展。通过集成线程池,重写线程池的beforeExecute、afterExecute和terminated方法。执行一些自定义的代码来监控。
例如监控任务的平均时间,最大(最小)执行时间等。在线程池中默认是空方法。
原创粉丝点击