1、概览
Java平台包括一个并发实用程序包。这些类被设计用来作为构建并发类或应用程序的构建块。正如集合框架通过提供常用数据结构的实现来简化内存中数据的组织和操作一样,并发实用程序通过提供并发设计中常用的构建块的实现来简化并发类的开发。并发实用程序包括高性能、灵活的线程池;异步执行任务的框架;一组为并发访问而优化的集合类;同步实用程序,如计数信号量;原子变量;锁;和条件变量。
使用并发实用程序,而不是自己开发线程池之类的组件,可以提供许多优点:
- 减少了编程工作。使用一个标准类要比自己开发它容易得多。
- 性能的提升。并发实用程序中的实现由并发性和性能专家开发并进行同行评审;这些实现可能比典型的实现更快、可伸缩性更强,即使是熟练的开发人员也不例外。
- 增加可靠性。开发并发类是困难的——Java语言提供的低级并发工具(synchronized、volatile、wait()、notify()和notifyAll()很难正确使用,使用这些工具产生的错误很难检测和调试。通过使用标准化的、经过广泛测试的并发性构建块,可以消除许多线程化危险的潜在来源,如死锁、饥饿、竞争条件或过多的上下文切换。
- 提高可维护性。使用标准库类的程序比那些依赖复杂的自定义类的程序更容易理解和维护。
- 提高生产力。开发人员可能已经了解了标准的库类,因此不需要了解特定并发组件的API和行为。此外,如果并发应用程序构建在可靠的、经过良好测试的组件上,那么调试起来会更简单。
简而言之,使用并发实用程序实现并发应用程序可以帮助您的程序更清晰、更短、更快、更可靠、更可伸缩、更容易编写、更容易阅读和更容易维护。
The concurrency utilities includes:
- 任务调度框架。Executor接口根据一组执行策略对异步任务的调用、调度、执行和控制进行标准化。提供的实现支持在提交线程、单个后台线程(如Swing中的事件)、新创建的线程或线程池中执行任务,并且开发人员可以创建支持任意执行策略的Executor的自定义实现。内置的实现提供了可配置的策略,如队列长度限制和饱和策略,这些策略可以通过防止失控的资源使用来提高应用程序的稳定性。
- Fork / join框架。基于ForkJoinPool类,这个框架是Executor的一个实现。它的设计目的是使用工作线程池有效地运行大量任务。 work-stealing 技术用于保持所有工作线程处于繁忙状态,以充分利用多个处理器。
- Concurrent collections。添加了几个新的集合类,包括新的Queue、BlockingQueue和BlockingDeque接口,以及Map、List和Queue的高性能并发实现。有关更多信息,请参见Collections Framework Guide 。
- 原子变量。提供了一些实用工具类,它们自动地操作单个变量(基本类型或引用),提供了高性能的原子算法和比较与设置方法。与使用同步(在大多数平台上)相比,java.util.concurrent.atomic包中的原子变量实现提供了更高的性能,这使得它们对于实现高性能并发算法和方便地实现计数器和序列号生成器非常有用。
- Synchronizers。通用的同步类,包括semaphores, barriers, latches, phasers, and exchangers,促进了线程之间的协调。
- 锁。虽然锁定是通过synchronized关键字构建到Java语言中的,但是对于内置的监视器锁有许多限制。 java.util.concurrent.locks包提供与同步具有相同内存语义的高性能锁实现。它还支持在尝试获取锁时指定超时、每个锁有多个条件变量、非嵌套(“手动”)持有多个锁,以及支持中断正在等待获取锁的线程。
- 纳秒粒度时间。 System.nanoTime方法允许访问纳秒级粒度的时间源,以便进行相对时间测量和接受超时的方法(例如 BlockingQueue.offer, BlockingQueue.poll, Lock.tryLock,tryLock,Condition.await和Thread.sleep)可以获取以纳秒为单位的超时值。System.nanoTime方法 的实际精度依赖于平台。
2、任务调度框架
Executor
Module java.base
Package java.util.concurrent
Interface Executor
执行已提交的可运行任务的对象。该接口提供了一种将任务提交与如何运行每个任务的机制(包括线程使用、调度等细节)分离的方法。通常使用Executor而不是显式地创建线程。
例如,相比一组任务中的每个任务调用new Thread(new RunnableTask()).start() ,采用如下的方法更好:
Executor executor = anExecutor();
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
...
但是,Executor接口并不严格要求执行是异步的。在最简单的情况下,executor可以立即在调用者的线程中运行提交的任务:
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
更典型的情况是,任务在调用者的线程之外的某个线程中执行。下面的执行程序为每个任务生成一个新线程。
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
许多Executor实现对如何以及何时调度任务施加了某种限制。下面的执行程序将任务的提交序列化到第二个执行程序,演示了复合执行程序。
class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new ArrayDeque<>();
final Executor executor;
Runnable active;
SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(Runnable r) {
tasks.add(() -> {
try {
r.run();
} finally {
scheduleNext();
}
});
if (active == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((active = tasks.poll()) != null) {
executor.execute(active);
}
}
}
此包中提供的Executor实现了ExecutorService,这是一个用处更加广泛的接口。
ThreadPoolExecutor类提供可扩展的线程池实现。
Executors 类为这些executor提供了方便的工厂方法。
ExecutorService
public interface ExecutorService extends Executor
ExecutorService是一个Executor, 它提供用于管理终止,以及可以产生用于跟踪一个或多个异步任务进展的Future的方法。ExecutorService可以被关闭,这将导致它拒绝新任务。提供了两种不同的方法来关闭ExecutorService。shutdown()方法允许在终止之前执行以前提交的任务,而shutdownNow()方法可以防止等待的任务启动并尝试停止当前正在执行的任务。在终止时,执行程序没有正在执行的任务,没有等待执行的任务,也不能提交新的任务。
方法submit扩展了基本方法Executor.execute(Runnable),创建并返回一个可用于取消执行和/或等待完成的Future。
方法invokeAny和invokeAll执行最常用的批量执行形式,执行一组任务,然后等待至少一个或全部任务完成。(类ExecutorCompletionService可用于编写这些方法的定制变体。)
Executors 类为这个包中提供的executor服务提供工厂方法。
使用案例:
下面是一个网络服务的例子,其中线程池服务中的线程传入请求。它使用预配置的Executors.newFixedThreadPool(int)工厂方法:
class NetworkService implements Runnable {
private final ServerSocket serverSocket;
private final ExecutorService pool;
public NetworkService(int port, int poolSize)
throws IOException {
serverSocket = new ServerSocket(port);
pool = Executors.newFixedThreadPool(poolSize);
}
public void run() { // run the service
try {
for (;;) {
pool.execute(new Handler(serverSocket.accept()));
}
} catch (IOException ex) {
pool.shutdown();
}
}
}
下面的方法分两个阶段关闭ExecutorService,首先调用shutdown来拒绝传入的任务,然后在必要时调用shutdownNow来取消任何延迟任务:
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
AbstractExecutorService
public abstract class AbstractExecutorService
extends Object
implements ExecutorService
提供ExecutorService执行方法的默认实现。这个类使用newTaskFor返回的RunnableFuture实现了submit、invokeAny和invokeAll方法,这个RunnableFuture默认是这个包中提供的FutureTask类。例如,submit(Runnable)的实现创建了一个执行并返回的关联的RunnableFuture。子类可以覆盖newTaskFor方法来返回RunnableFuture实现,而不是FutureTask。
下面是一个类的示意图,它定制ThreadPoolExecutor来使用CustomTask类,而不是默认的FutureTask:
public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
static class CustomTask<V> implements RunnableFuture<V> {...}
protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) {
return new CustomTask<V>(c);
}
protected <V> RunnableFuture<V> newTaskFor(Runnable r, V v) {
return new CustomTask<V>(r, v);
}
// ... add constructors, etc.
}
ThreadPoolExecutor
public class ThreadPoolExecutor extends AbstractExecutorService
使用可能的几个池线程之一执行每个提交的任务的ExecutorService,通常使用exec工厂方法进行配置。
线程池解决了两个不同的问题:由于减少了每个任务的调用开销,线程池通常在执行大量异步任务时提供了更好的性能,并且提供了一种绑定和管理资源(包括执行任务集合时消耗的线程)的方法。
为了在广泛的上下文中有用,这个类提供了许多可调参数和可扩展性挂钩。然而,程序员要求使用更方便的Executors 工厂方法Executors.newCachedThreadPool()(无界的线程池,线程自动回收),Executors.newFixedThreadPool (int)(固定大小的线程池)和Executors.newSingleThreadExecutor()(单个后台线程),preconfigure设置为最常见的使用场景。否则,在手动配置和调优该类时,请使用以下指南:
-
Core and maximum pool sizes。
ThreadPoolExecutor将根据corePoolSize(参见getCorePoolSize())和maximumPoolSize(参见getMaximumPoolSize())设置的界限自动调整池大小(参见getPoolSize())。在方法execute(Runnable)中提交新任务时,如果运行的线程小于corePoolSize,则创建一个新线程来处理请求,即使其他工作线程处于空闲状态。
否则,如果运行的线程小于maximumPoolSize,则只在队列满时创建一个新线程来处理请求。
通过将corePoolSize和maximumPoolSize设置为相同的值,您可以创建一个固定大小的线程池。通过将maximumPoolSize设置为一个基本无界的值,如Integer.MAX_VALUE,允许池容纳任意数量的并发任务。最典型的情况是,仅在构建时设置核心和最大池大小,但也可以使用setCorePoolSize(int)和setMaximumPoolSize(int)动态更改它们。 -
按需构建
默认情况下,即使是核心线程也只是在新任务到达时才创建和启动,但是可以使用方法prestartCoreThread()或prestartAllCoreThreads()动态地覆盖它。如果使用非空队列构造池,则可能需要预启动线程。 -
创建新线程
使用ThreadFactory创建新线程。如果没有另外指定,将使用executols . defaultthreadfactory(),它将创建所有属于相同ThreadGroup的线程,并且具有相同的NORM_PRIORITY优先级和非守护进程状态。通过提供不同的ThreadFactory,您可以更改线程的名称、线程组、优先级、守护进程状态等。如果ThreadFactory通过从newThread返回null来创建一个线程失败,执行程序将继续执行,但可能无法执行任何任务。线程应该拥有“modifyThread”RuntimePermission。如果工作线程或使用池的其他线程不拥有此权限,服务可能会降级:配置更改可能不会及时生效,关闭池可能仍然处于可能终止但尚未完成的状态。 -
保持存活时间
如果当前池中有超过corePoolSize的线程,那么如果空闲时间超过keepAliveTime(请参阅getKeepAliveTime(TimeUnit)),则多余的线程将被终止。这提供了一种在池没有被积极使用时减少资源消耗的方法。如果以后池变得更活跃,就会构造新的线程。还可以使用setKeepAliveTime(long, TimeUnit)方法动态更改此参数。使用Long.MAX_VALUE TimeUnit.NANOSECONDS有效地禁止空闲线程在关闭之前终止。默认情况下,keep-alive策略只适用于拥有多于corePoolSize线程的情况,但是也可以使用allowCoreThreadTimeOut(boolean)方法将这个超时策略应用于核心线程,只要keepAliveTime值不为零。 -
队列
任何BlockingQueue都可以用来传输和保存提交的任务。此队列的使用与池大小调整相互作用。- 如果运行的线程小于corePoolSize,则执行程序总是希望添加新线程而不是排队
- 如果正在运行的线程数目大于等于corePoolSize,执行程序总是希望对请求进行排队,而不是添加新线程。
- 如果一个请求不能排队,那么将创建一个新线程,除非这个线程的大小超过maximumPoolSize,在这种情况下,任务将被拒绝。
排队有三种基本策略:
- 直接的传递。工作队列的一个很好的默认选择是SynchronousQueue,它将任务传递给线程,而不会占用线程。在这里,如果没有立即可用的线程来运行任务,则对任务进行排队的尝试将失败,因此将构造一个新线程。此策略在处理可能具有内部依赖项的请求集时避免锁定。直接移交通常需要无界的最大池大小,以避免拒绝新提交的任务。反过来,当命令到达的平均速度比它们被处理的速度还要快时,就有可能出现无限的线程增长。
- 无界队列。当所有的corePoolSize线程都处于繁忙状态时,使用无界队列(例如没有预定义容量的LinkedBlockingQueue)将导致新任务在队列中等待。因此,创建的线程不会超过corePoolSize。(因此,maximumPoolSize的值没有任何影响。)当每个任务完全独立于其他任务时,这可能是合适的,因此任务不会影响其他任务的执行;例如,在web页面服务器中。虽然这种类型的队列在平滑短暂的请求突发方面很有用,但它也承认,当命令平均到达速度超过处理速度时,可能会出现无限的工作队列增长。
- 有界的队列。有限的队列(例如,ArrayBlockingQueue)在使用有限的最大池大小时有助于防止资源耗尽,但是调优和控制可能更困难。队列大小和最大池大小可以相互交换:使用大队列和小池可以最小化CPU使用、操作系统资源和上下文切换开销,但是会导致人为的低吞吐量。如果任务经常阻塞(例如,它们受到I/O的限制),系统可能会为比其他方式允许的更多的线程安排时间。使用小队列通常需要更大的池大小,这会使cpu更忙,但可能会遇到无法接受的调度开销,这也会降低吞吐量。
-
拒绝接受任务
在方法execute(Runnable)中提交的新任务将在执行程序关闭时被拒绝,并且在执行程序对最大线程和工作队列容量使用有限的界限并达到饱和时也会被拒绝。在这两种情况下,execute方法都会调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)方法的RejectedExecutionHandler。提供四个预定义的处理程序策略:- 在默认的ThreadPoolExecutor.AbortPolicy中。处理程序在拒绝时抛出运行时RejectedExecutionException异常。
- 在 ThreadPoolExecutor.CallerRunsPolicy中。调用execute本身的线程将运行该任务。这提供了一个简单的反馈控制机制,可以降低新任务的提交速度。
- 在ThreadPoolExecutor.DiscardPolicy中。无法执行的任务将被丢弃。
- 在ThreadPoolExecutor.DiscardOldestPolicy中,如果执行器没有关闭,工作队列头部的任务将被删除,然后重新执行(可能再次失败,导致重复执行)。
可以定义和使用其他类型的RejectedExecutionHandler类。这样做需要谨慎,特别是当策略被设计为仅在特定容量或队列策略下工作时。
-
Hook methods
该类提供protected的可覆盖的beforeExecute(Thread, Runnable) 和afterExecute(Runnable, Throwable) 方法,这些方法在每个任务执行之前和之后调用。这些可以用来操作执行环境;例如,重新初始化threadlocal、收集统计信息或添加日志条目。此外,可以覆盖terminate()方法,以便在执行程序完全终止后执行需要执行的任何特殊处理。
如果hook, callback, or BlockingQueue方法抛出异常,则内部工作线程可能会失败、突然终止并可能被替换。 -
队列的维护
方法getQueue()允许访问工作队列,以便进行监视和调试。强烈反对将此方法用于任何其他目的。提供的两个方法remove(Runnable)和purge()可用于在大量排队的任务被取消时帮助回收存储。 -
回收
不再在程序中引用并且没有剩余线程的池可以被回收(垃圾回收),而不需要显式关闭。您可以配置一个池,通过设置适当的keep-alive时间(使用0个核心线程的下限)和/或设置allowCoreThreadTimeOut(布尔值)来允许所有未使用的线程最终死亡。
扩展的例子。该类的大多数扩展都会覆盖一个或多个受保护的钩子方法。例如,这里有一个子类,添加了一个简单的暂停/恢复功能:
class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();
public PausableThreadPoolExecutor(...) { super(...); }
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused) unpaused.await();
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}
public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}