该系列文章为翻译自国外博客http://www.baeldung.com
1.概览
java.util.concurrent 包提供了创建多线程应用所需的工具, 在本文中,我们会对整个java.util.concurrent包做一个概览。
2.核心组件
. Executor
.ExecutorService
.ScheduledExecutorService
.Future
.CountDownLatch
.CyclicBarrier
.Semaphore
.ThreadFactory
.BlockingQueue
.DelayQueue
.Locks
.Phaser
针对上述的每一个组件,我们都用专题文章。
2.1 Executor
Executor代表的是一个能执行给定任务的接口(interface)。一个任务应该在一个新线程上运行还是在一个并发线程上运行,取决于特定的实现(调用从何处开始) 。因此,使用这个接口我们可以把任务执行流从实际的任务执行链中相分离。
不过有一点需要注意,Executor并不严格地要求任务执行一定要是异步的。在下面这个简单得例子中,一个executor可以立即在当前调用的线程中执行已提交的任务task.
我们需要建立一个invoker去创建executor实例:
publicclassInvoker implementsExecutor {
@Override
publicvoidexecute(Runnable r) {
r.run();
}
}
现在,我们就可以使用这个invoker来执行任务啦。
publicvoidexecute() {
Executor executor = newInvoker();
executor.execute( () -> {
// 要执行的任务
});
}
注意:如果这个executor不能接受这个要执行的任务,它会抛出RejectedExecutionException异常。
2.2 ExecutorService
ExecutorService是一个针对异步处理的完整解决方案,它负责管理一个内存队列并且基于线程的可用性调度已提交的任务。
在使用ExecutorService 之前,我们需要创建一个Runnable类.
publicclassTask implementsRunnable {
@Override
publicvoidrun() {
// task details
}
}
现在我们就能创建一个ExecutorService实例并分配这个任务。在创建的时候,我们需要指定线程池的大小。
ExecutorService executor = Executors.newFixedThreadPool(10);
如果你想创建一个单线程的ExecutorService实例,我们可以使用new SingleThreadExecutor(ThreadFactory threadFactory) 创建这个实例。
一旦这个executor被创建,我们就可以使用它来提交任务了。
publicvoidexecute() {
executor.submit(newTask());
}
当然,我们也能在提交任务的时候,创建这个Runnable实例、
executor.submit(() -> {
newTask();
});
它同时也提供了两个十分便捷地结束运行的方法,第一个是shutdown(),它会一直等待到所有被提交的任务结束运行。另一个方法是 shutdownNow() ,这个方法会立即终结所有的pending或executing 的任务。
还有一个方法 awaitTermination(long timeout,TimeUnit unit) ,在一个关闭时间触发或 执行超时发生 又或者 执行线程自身被中断之后, 这个方法 会强制阻塞直到所有的所有的任务完成运行。
try{
executor.awaitTermination( 20l, TimeUnit.NANOSECONDS );
} catch(InterruptedException e) {
e.printStackTrace();
}
2.3 ScheduledExecutorService
ScheduledExecutorService 是一个类似于 ExecutorService的接口,但是它可以周期性地执行任务。Executor和ExecutorService的方法会立刻被调度没有任何的人为延迟。0或任何负数都表明这个请求需要立即执行。
我们可以使用Runnable和Callable接口来定义task:
publicvoidexecute() {
ScheduledExecutorService executorService
= Executors.newSingleThreadScheduledExecutor();
Future future = executorService.schedule(() -> {
// ...
return"Hello world";
}, 1, TimeUnit.SECONDS);
ScheduledFuture scheduledFuture = executorService.schedule(() -> {
// ...
}, 1, TimeUnit.SECONDS);
executorService.shutdown();
}
ScheduledExecutorService也可以在一个给定的延迟时间后再调度这个任务:
executorService.scheduleAtFixedRate(() -> {
// ...
}, 1, 10, TimeUnit.SECONDS);
executorService.scheduleWithFixedDelay(() -> {
// ...
}, 1, 10, TimeUnit.SECONDS);
这里,scheduleAtFixedRate( Runnable command, long initialDelay, long period, TimeUnit unit ) 方法会创建并执行一个周期性的行为,
在给定的初始延迟后,它会被首次执行,紧接着在 给定的周期内陆续被调用,直到这个服务实例关闭(shutdown).
scheduleWithFixedDelay( Runnable command, long initialDelay, long delay, TimeUnit unit ) 方法会创建并执行一个周期性的行为。
在给定的初始延迟后被首次调用,并在再给定的正在执行的和下一个被调用的之间的延迟时间内重复。
2.4 Future
Future是用于表示一个异步操作的结果。它同时还伴随有检查异步操作是否完成的方法以及获取计算结果的方法等等。另外,cancel(boolean mayInterruptIfRunning) 这个API会取消操作并且释放正在执行的线程。如果mayInterruptIfRunning的值为true的话,正在执行任务的线程将会被立即终结。否则的话,就允许进行中的任务完成。
我们可以使用下面的代码片去创建一个future 实例:
publicvoidinvoke() {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future future = executorService.submit(() -> {
// ...
Thread.sleep(10000l);
return"Hello world";
});
}
我们可以使用下面的代码片去检查这个future结果已经准备好并且在运算完成时,获取数据:
if(future.isDone() && !future.isCancelled()) {
try{
str = future.get();
} catch(InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
我们也能为一个指定的操作指定超时时间,如果这个任务花费的时间超过了指定值,就会抛出
一个TimeoutException:
try{
future.get(10, TimeUnit.SECONDS);
} catch(InterruptedException | ExecutionException | TimeoutException e) {
e.printStackTrace();
}
2.5 CountDownLatch
CountDownLatch 是一个功能类,它能阻塞一组线程直到一些操作完成。CountDownLatch在初始化时,会伴随着一个counter(Integer type);
当依赖的线程结束运行时,该计数器会递减。但是,一旦计数器的值到达0,其他线程就会被释放。关于CountDownLatch的详情请点击这里。
2.6 CyclicBarrier
CyclicBarrier 的作用几乎和CountDownLatch 一样,除了 我们可以复用它。 不像CountDownLatch,在调用最终的任务之前,它允许多线程使用 await()方法(就是大家所熟知的 barrier 条件) 等待彼此。我们需要创建一个Runnable task实例 来初始化这个barrier 条件:
publicclassTask implementsRunnable {
privateCyclicBarrier barrier;
publicTask(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
publicvoidrun() {
try{
LOG.info(Thread.currentThread().getName() +
" is waiting");
barrier.await();
LOG.info(Thread.currentThread().getName() +
" is released");
} catch(InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
现在我们可以调用一些线程去竞争这个barrier 条件:
publicvoidstart() {
CyclicBarrier cyclicBarrier = newCyclicBarrier(3, () -> {
// ...
LOG.info("All previous tasks are completed");
});
Thread t1 = newThread(newTask(cyclicBarrier), "T1");
Thread t2 = newThread(newTask(cyclicBarrier), "T2");
Thread t3 = newThread(newTask(cyclicBarrier), "T3");
if(!cyclicBarrier.isBroken()) {
t1.start();
t2.start();
t3.start();
}
}
这里,isBroken()方法会检查是否有线程在执行期间被中断,我们应该在执行实际的处理之前先执行该检查。
2.7 Semaphore
Semaphore是用于阻塞对某些物理或逻辑资源的线程级别的访问。一个semaphore包含了一套许可证;当一个线程试图进入临界区时,它需要检查
该semaphore,以便于知道有没有可用的许可证。
如果许可证不可用的话(通过 tryAcquire()),线程不允许跳到临界区;然而,如果许可证可用,访问被准许的话,该许可证计数器(the permit counter)就会减少.
一旦正在执行的线程释放临界区,许可证计数器的值又会被再一次增加(通过release()方法来完成)。
通过使用 tryAcquire(long timeout,TimeUnit unit)方法,我们在获取访问时指定超时时间。
下面的代码片段可用于实现一个semaphore:
staticSemaphore semaphore = newSemaphore(10);
publicvoidexecute() throwsInterruptedException {
LOG.info("Available permit : "+ semaphore.availablePermits());
LOG.info("Number of threads waiting to acquire: "+
semaphore.getQueueLength());
if(semaphore.tryAcquire()) {
semaphore.acquire();
// ...
semaphore.release();
}
}
使用semaphore,我们可以实现一个像数据结构一样的互斥量。更多详情,请看。
2.8 ThreadFactory 线程工厂
正如名字所提示的意思,ThreadFactory 表现的就像线程池一样,它会在需要时创建新线程。在实现有效的线程创建链时,ThreadFactory消除了许多样板化的代码。
我们可以定义一个ThreadFactory:
publicclassBaeldungThreadFactory implementsThreadFactory {
privateintthreadId;
privateString name;
publicBaeldungThreadFactory(String name) {
threadId = 1;
this.name = name;
}
@Override
publicThread newThread(Runnable r) {
Thread t = newThread(r, name + "-Thread_"+ threadId);
LOG.info("created new thread with id : "+ threadId +
" and name : "+ t.getName());
threadId++;
returnt;
}
}
我们可以使用这个newThread(Runnable r)方法在运行时去创建一个新线程:
BaeldungThreadFactory factory = newBaeldungThreadFactory(
"BaeldungThreadFactory");
for(inti = 0; i < 10; i++) {
Thread t = factory.newThread(newTask());
t.start();
}
2.9 BlockingQueue 阻塞队列
在异步编程中,使用最普遍的模式就是 生产者-消费者模式。 java.util.concurrent 包中提供的有一个数据类型即众所周知的BlockingQueue -它在这些异步场景中非常有用。
2.10 DelayQueue延迟队列
DelayQueue是一个无穷大的阻塞队列,它里面的元素只有在它的过期时间完成时,才会被拉过来。因此,最顶层的元素(头)将会拥有最大的延迟,同时它也是最后一个被投票的。
2.11 Locks 锁
锁Lock是用于阻塞其他线程访问某一段特定代码的小工具,用于和当前正在持有锁线程分隔开。 Lock和Synchronized block同步代码块最主要的区别是,同步代码块主要是被包含在方法中,而Lcok的API lock()以及unlock()方法却可以在分开的方法中使用。
2.12 Phaser
比起CyclicBarrier和CountDownLatch来说,Phaser是一个更灵活的解决方案 -它可以作为一个可复用的barrier。动态线程数在继续执行之前需要等待这个barrier.我们可以协同执行的多个phase,为每一个program phase复用一个 Phaser实例。
3. 结论 Conclusion
在这篇概览文章中,我们聚焦了java.util.concurrent包中的几个不同特性,和往常一样,全部的源代码都在在github上,地址是:https://github.com/eugenp/tutorials/tree/master/core-java-concurrency