Java并发编程实践 (2)

关闭钩子应该是线程安全的。它们在访问共享数据时必须使用同步机制,并且小心地避免发生死锁,这与其他并发代码的要求相同。而且,关闭钩子不应该对应用程序的状态或者JVM的关闭原因做出任何假设,因此在编写关闭钩子的代码时必须考虑周全。

关闭ExecutorService

ExecutorService提供了两种关闭方法:

ExecutorService.shutdown():正常关闭

ExecutorService.shutdownNow():强行关闭
这两种关闭方式的差别在于各自的安全性响应性:强行关闭的速度更快,但风险也更大,因为任务很可能在执行到一半时被结束;而正常关闭虽然速度慢,但却更安全,因为ExecutorService会一直等到队列中的所有任务都执行完成后才关闭。在其他拥有线程的服务中也应该考虑提供类似的关闭方式以供选择。

\(\circ\) 正常关闭

try{ // 正常关闭 executorService.shutdown(); // 等待指定时间直到结束,超时会抛出InterruptedException异常 executorService.awaitTermination(timeout, unit); }catch(InterruptedException ex){ // do something }

\(\circ\) 强行关闭

try{ // 强行关闭 List<Runnable> unfinishedTasks = executorService.shutdownNow(); // 处理未完成的任务 handle(unfinishedTasks); // 等待指定时间直到结束,超时会抛出InterruptedException异常 executorService.awaitTermination(timeout, unit); }catch(InterruptedException ex){ // do something } 资源释放 调用的方法 锁 CPU
Thread.sleep()   不释放   释放  
Thread.join()   不释放   释放  
Thread.yield()   不释放   释放  
Object.wait()   释放   释放  
Condition.await()   释放   释放  
ThreadPoolExecutor public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

corePoolSize 线程池核心线程大小

在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,(除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程)。

默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。

maximumPoolSize 线程池最大线程数

当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maximumPoolSize。如果线程数已等于maximumPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。

keepAliveTime 空闲线程存活时间

当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。

unit 空间线程存活时间单位

keepAliveTime的计量单位

workQueue 工作队列

新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。JDK中提供了四种工作队列:

\(\circ\) ArrayBlockingQueue 基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maximumPoolSize,则会执行拒绝策略。

\(\circ\) LinkedBlockingQuene 基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maximumPoolSize,因此使用该工作队列时,参数maximumPoolSize其实是不起作用的。

\(\circ\) SynchronousQuene 一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maximumPoolSize,则执行拒绝策略。

\(\circ\) PriorityBlockingQueue 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

threadFactory 线程工厂

创建一个新线程时使用的工厂,可以用来设定线程名是否为daemon线程、Thread.UncaughtExceptionHandler等等。

handler 拒绝策略

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywjsw.html