1. 概述
该的java.util.concurrent包提供了创建并发应用程序的工具。
在本文中,我们将对整个包进行概述。
2. 主要部件
该java.util.concurrent中包含了太多的功能,在一个写了讨论。在本文中,我们将主要关注此包中一些最有用的实用程序,如:
- Executor
- ExecutorService
- ScheduledExecutorService
- Future
- CountDownLatch
- CyclicBarrier
- Semaphore
- ThreadFactory
- BlockingQueue
- DelayQueue
- Locks
- Phaser
您还可以在此处找到许多专门针对各个课程的文章。
2.1 Executor
Executor是一个表示执行提供的任务的对象的接口。
如果任务应在新线程或当前线程上运行,则它取决于特定实现(从启动调用的位置)。因此,使用此接口,我们可以将任务执行流与实际任务执行机制分离。
这里要注意的一点是,Executor并不严格要求任务执行是异步的。在最简单的情况下,执行程序可以在调用线程中立即调用提交的任务。
我们需要创建一个调用者来创建执行者实例:
public class Invoker implements Executor {
@Override
public void execute(Runnable r) {
r.run();
}
}
现在,我们可以使用此调用程序来执行任务。
public void execute() {
Executor executor = new Invoker();
executor.execute( () -> {
// task to be performed
});
}
这里要注意的是,如果执行程序不能接受执行任务,它将抛出RejectedExecutionException。
2.2 ExecutorService
ExecutorService是异步处理的完整解决方案。它管理内存中队列并根据线程可用性计划提交的任务。
要使用ExecutorService,我们需要创建一个Runnable类。
public class Task implements Runnable {
@Override
public void run() {
// task details
}
}
现在我们可以创建ExecutorService实例并分配此任务。在创建时,我们需要指定线程池大小。
ExecutorService executor = Executors.newFixedThreadPool(10);
如果我们要创建单线程ExecutorService实例,我们可以使用newSingleThreadExecutor(ThreadFactory threadFactory)来创建实例。
创建执行程序后,我们可以使用它来提交任务。
public void execute() {
executor.submit(new Task());
}
我们还可以在提交任务时创建Runnable实例。
executor.submit(() -> {
new Task();
});
它还带有两个开箱即用的执行终止方法。第一个是shutdown() ; 它等待所有提交的任务完成执行。另一种方法是执行shutdownNow() whic ħ立即终止所有未决/执行的任务。
还有另一种方法awaitTermination(长超时,TimeUnit单元)强制阻塞,直到所有任务在触发关闭事件或执行超时发生后完成执行,或者执行线程本身被中断,
try {
executor.awaitTermination( 20l, TimeUnit.NANOSECONDS );
} catch (InterruptedException e) {
e.printStackTrace();
}
2.3 ScheduledExecutorService的
ScheduledExecutorService是与ExecutorService类似的接口,但它可以定期执行任务。
Executor和ExecutorService的方法是在现场安排的,没有引入任何人工延迟。零或任何负值表示需要立即执行请求。
我们可以使用Runnable和Callable接口来定义任务。
public void execute() {
ScheduledExecutorService executorService
= Executors.newSingleThreadScheduledExecutor();
Future<String> 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命令,long initialDelay,long period,TimeUnit unit)方法创建并执行一个周期性操作,该操作在提供的初始延迟之后首先被调用,随后是给定的时间段,直到服务实例关闭。
所述scheduleWithFixedDelay(可运行命令,长在initialDelay,长的延迟,TIMEUNIT单元)方法与所述执行的一个的终止和的调用之间的给定的延迟创建并执行所提供的初始延迟后首先调用的周期性动作,并重复地下一个。
2.4 Future
Future用于表示异步操作的结果。它带有检查异步操作是否完成,获取计算结果等的方法。
而且,cancel(boolean mayInterruptIfRunning) API取消操作并释放执行线程。如果mayInterruptIfRunning的值为true,则执行任务的线程将立即终止。
public void invoke() {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future<String> future = executorService.submit(() -> {
// ...
Thread.sleep(10000l);
return "Hello world";
});
}
我们可以使用以下代码片段来检查未来结果是否准备就绪,并在计算完成后获取数据:
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(在JDK 5中引入)是一个实用程序类,它阻止一组线程,直到某些操作完成。
甲CountDownLatch初始化为计数器(整数型); 当依赖线程完成执行时,此计数器递减。但是一旦计数器达到零,其他线程就会被释放。
您可以在此处了解有关CountDownLatch的 更多信息。
2.6 CyclicBarrier
CyclicBarrier与CountDownLatch几乎相同,只是我们可以重用它。与CountDownLatch不同,它允许多个线程在调用最终任务之前使用await()方法(称为障碍条件)彼此等待。
我们需要创建一个Runnable任务实例来启动屏障条件:
public class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
LOG.info(Thread.currentThread().getName() +
" is waiting");
barrier.await();
LOG.info(Thread.currentThread().getName() +
" is released");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
现在我们可以调用一些线程来竞争障碍条件:
public void start() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
// ...
LOG.info("All previous tasks are completed");
});
Thread t1 = new Thread(new Task(cyclicBarrier), "T1");
Thread t2 = new Thread(new Task(cyclicBarrier), "T2");
Thread t3 = new Thread(new Task(cyclicBarrier), "T3");
if (!cyclicBarrier.isBroken()) {
t1.start();
t2.start();
t3.start();
}
}
这里,isBroken()方法检查在执行期间是否有任何线程被中断。在执行实际过程之前,我们应该始终执行此检查。
2.7 Semaphore
的信号量被用于阻挡到物理或逻辑资源的某些部分螺纹级别的访问。信号量包含一组许可证; 每当线程试图进入临界区时,如果有可用许可证,它需要检查信号量。
如果没有许可证(通过tryAcquire()),则不允许该线程跳入临界区; 但是,如果许可证可用,则授予访问权限,许可证计数器减少。
一旦执行线程释放临界区,则许可计数器再次增加(由release()方法完成)。
我们可以使用tryAcquire(long timeout,TimeUnit unit)方法指定获取访问权限的超时时间。
我们还可以检查可用许可证的数量或等待获取信号量的线程数。
以下代码片段可用于使用实现信号量:
static Semaphore semaphore = new Semaphore(10);
public void execute() throws InterruptedException {
LOG.info("Available permit : " + semaphore.availablePermits());
LOG.info("Number of threads waiting to acquire: " +
semaphore.getQueueLength());
if (semaphore.tryAcquire()) {
semaphore.acquire();
// ...
semaphore.release();
}
}
我们可以使用Semaphore实现类似Mutex的数据结构。有关这方面的更多细节可以在这里找到。
2.8 ThreadFactory
顾名思义,ThreadFactory充当线程(不存在)池,它根据需要创建新线程。它消除了大量样板编码的需要,以实现有效的线程创建机制。
我们可以定义一个ThreadFactory:
public class BaeldungThreadFactory implements ThreadFactory {
private int threadId;
private String name;
public BaeldungThreadFactory(String name) {
threadId = 1;
this.name = name;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, name + "-Thread_" + threadId);
LOG.info("created new thread with id : " + threadId +
" and name : " + t.getName());
threadId++;
return t;
}
}
我们可以使用这个newThread(Runnable r)方法在运行时创建一个新线程:
BaeldungThreadFactory factory = new BaeldungThreadFactory(
"BaeldungThreadFactory");
for (int i = 0; i < 10; i++) {
Thread t = factory.newThread(new Task());
t.start();
}
2.9 BlockingQueue
在异步编程中,最常见的集成模式之一是生产者 - 消费者模式。该的java.util.concurrent包带有一个数据结构所知道的BlockingQueue的 -它可以在这些异步情况下非常有用的。
有关这方面的更多信息和工作示例,请点击此处。
2.10 DelayQueue
DelayQueue是一个无限大小的元素阻塞队列,只有当元素的到期时间(称为用户定义的延迟)完成时才能被拉出。因此,最顶部的元素(头部)将具有最大量的延迟并且将最后轮询。
有关这方面的更多信息和工作示例,请点击此处。
2.11 Lock
毫不奇怪,Lock是一个实用程序,用于阻止其他线程访问某段代码,除了当前正在执行它的线程。
Lock和Synchronized块之间的主要区别在于synchronized块完全包含在方法中; 但是,我们可以在单独的方法中使用Lock API的lock()和unlock()操作。
有关这方面的更多信息和工作示例,请点击此处。
2.12 CountDownLatch
Phaser是比CyclicBarrier和CountDownLatch更灵活的解决方案- 用作可重复使用的屏障,动态线程数在继续执行之前需要等待。我们可以协调多个执行阶段,为每个程序阶段重用Phaser实例。
有关这方面的更多信息和工作示例,请点击此处。
3.结论
在这篇高级概述文章中,我们专注于java.util.concurrent包的不同实用程序。
与往常一样,GitHub上提供了完整的源代码。
关注公众号:「Java知己」,每天更新Java知识哦,期待你的到来!
- 发送「1024」,免费领取 30 本经典编程书籍。
- 发送「Group」,与 10 万程序员一起进步。
- 发送「JavaEE实战」,领取《JavaEE实战》系列视频教程。
- 发送「玩转算法」,领取《玩转算法》系列视频教程。