多线程-高级

执行器 Executor

并发 API 提供了一种被称为 执行器 的特性,用于启动并发控制线程的执行。
执行器体系的核心是 Executor 接口,它是整个体系的顶层接口,其下有子接口和各种实现类。
Executor 框架关系图:Executor 接口 <|-- ExecutorService 接口 <|-- ScheduledExecutorService 接口
ExecutorService 接口添加了用于帮助管理和控制线程的方法。
ScheduledExecutorService 接口添加了一些支持线程调度的方法。
ExcecutorService 接口 <|… ThreadPoolExecutor 类ExecutorService 接口 <|… ForkJoinPool 类ScheduledExecutorService 接口 <|… ScheduledThreadPoolExecutor 类
大多数情况下,都是通过调用 Executors 工具类中所定义的静态工厂方法来获取执行器:
static ExecutorService newCachedThreadPool()
static ExecutorService newFixedThreadPool(int nThreads)
static ScheduledExecutorService newScheduledThreadPool(intcorePoolSize)newCachedThreadPool() 方法创建的线程池可以根据需要添加线程,但是会尽可能地重用线程。newFixedThreadPool() 方法创建指定数量线程的线程池。newScheduledThreadPool() 方法创建支持线程调度的线程池。Executor 接口体系中,最重要的方法是void execute(Runnable thread)由 thread 指定的线程将被执行。使用 Callable 和 Future 接口Callable 接口表示返回值的线程。应用程序可以使用 Callable 对象计算结果,然后将结果返回给调用线程。这种机制简化了数值计算类型的同步程序。另外,还可以用于运行那些返回状态码的线程。Callable 是泛型接口:interface Callable其中,V 指明了由任务返回的数据的类型。Callable 只定义了一个方法:call()V call() throws Exception在 call() 方法中定义希望执行的任务,在任务完成后返回结果。如果不能返回结果,那么 call() 方法必须抛出异常。Callable 任务通过对 ExecutorService 对象调用 submit() 方法来执行。submit() 有 3 种形式,但只有一种用于执行 Callable 任务。 Future submit(Callable task)​
Future future = service.submit(new Task());
Integer ret = future.get();

service.shutdown();

public class Task implements Callable {
@Override
public Integer call() throws Exception {

}
}
​任务的结果通过 Future 对象得以返回。Future 是泛型接口,表示将由 Callable 对象返回的值:interface Future其中 V 指定了结果的类型。为了获得返回值,需要调用 Future 的 get() 方法,该方法具有以下两种形式:V get() throws …V get(long wait, TimeUnit unit) throws …第一种形式无限期阻塞等待,第二种形式只等待指定时长。锁Java 并发 API 对 锁 提供了支持。锁是一些对象,它们为 synchronized 提供了替代方案。锁的工作原理如下:在访问共享资源之前,申请用于保护资源的锁;当资源访问结束完成时,释放锁。当某个线程正在使用锁时,如果另一个线程尝试申请锁,那么后者将会阻塞等待,直到锁被前者释放位置。所有的锁都要实现 Lock 接口。方法描述void lock()进行等待,直到可以获得锁为止void lockInterruptibly() throws …除非被中断,否则进行等待,直到可以获得锁为止Condition newCondition()返回与调用锁关联的 Condition 对象boolean tryLock()尝试获得锁,如果锁不可获得,立即返回false;如果可获得,返回trueboolean tryLock(long wait, TimeUnit unit) throws …在指定时间内,尝试获得锁。如果超出时间后仍无法获得,则返回false;如果可获得,则返回truevoid unlock()释放锁并发 API 提供了 Lock 接口的一个实现,名为 ReentrantLock。它实现了一种可重入锁,当前持有锁的线程能够重复进入这种锁。当然,对于线程重入锁而言,所有 lock() 调用必须有相同数量的 unlock() 调用进行抵消。通过 Semaphore 实现同步Semaphore 实现了经典的信号量。信号量通过计数器控制对共享资源的访问。如果计数器大于0,访问是允许的;如果为0,访问时拒绝的。计数器的计数是允许访问共享资源的许可证。Semaphore 类具有如下所示的两个构造函数:Semaphore(int num)Semaphore(int num, boolean how)其中,num 指定了初始许可证计数大小。即,num 表示任意时刻能够访问共享资源的线程数量。默认情况下,阻塞的线程以随机的顺序“抢夺”许可证。通过将 how 设置为 true,可以确保线程以它们当去请求的先后顺序获得许可证。为了获得许可证,可以调用 acquire() 方法,该方法具有以下两种形式:void acquire() throws InterruptedExceptionvoid acquire(int num) throws InterruptedException第一个形式获得一个许可证。第二个形式获得 num 个许可证。在绝大多数情况下,使用第一种形式。如果在调用时无法取得许可证,就挂起当前线程,直到许可证可以获取为止。为了释放许可证,可以调用 release() 方法,该方法具有以下两种形式:void release();void release(int num);第一种形式是释放一个许可证。第二种形式是释放 num 个许可证。为了使用信号量控制对资源的访问,在访问资源之前,希望使用资源的每个线程必须首先调用 acquire() 方法。当线程使用完资源之后,必须调用 release() 方法。通过 CountDownLatch 实现同步有时会希望线程进行等待,直到发生一个或多个事情为止。为了处理这类情况,并发 API 提供了 CountDownLatch 类。CountDownLatch 在初始创建时带有事件计数器,在释放锁存器之前,必须发生指定数量的事件。每次发生一个事件时,计数器递减。直到计数器达到 0 时,打开锁存器。CountDownLatch 类具有以下构造函数:CountDownLatch(int num)其中,num 指定了为打开锁存器而必须发生的事件数量。为了等待锁存器,线程必须调用 await() 方法,该方法会阻塞当前线程:void await() throws InterruptedExceptionboolean await(long timeout, TimeUnit unit)对于第一种形式,直到与调用 CountDownLatch 所代表的计数器达到 0 才结束等待。第二种形式值等待由 timeout 指定的特定时间。unit 是一个枚举型变量,表示某种时间单位。如果到达时间限制,将返回 false;如果到达时间限制之前计数,递减为0,将返回 true 。为了表示触发了事件,可以调用 countDown() 方法:void countDown()使用 CyclicBarrier在并发编程中,会出现下面这种需求:具有两个或多个线程的线程组,必须在预定的执行点进行等待,直到线程组中的所有线程都到达了执行点为止。为了处理这种情况,并发 API 提供了 CyclicBarrier 类。使用 CyclicBarrier 类可实现以下功能:当前线程会被阻塞,直到指定数量的线程都到达临界点为止。CyclicBarrier 具有以下两个构造函数:CyclicBarrier(int numThreads);CyclicBarrier(int numThreads, Runnable action);其中 numThreads 指定了在继续执行之前必须到达界限点的线程数量。在第二种形式中,action 指定了当到达界限点后将要执行的线程。当某个每个线程到达界限点时,对 CyclicBarrier 对象调用 await() 方法。这将阻塞当前线程,直到其它所有线程也调用 await() 为止。一旦指定数量的线程到达界限点,await() 方法将返回并恢复当前线程的执行。此外,如果已经指定某个操作,那么将会执行那个线程。await() 方法有两种形式:public int await() throws …public int await(long timeout, TimeUnit unit) throws …第一种形式会持续等待,等待所有线程都到达界限点。第二种形式只会等待指定时间。这两个方法都返回一个int值,用于指示当前线程到达界限点的顺序。第一个到达返回 (numThreads - 1),最后一个到达返回0.使用 Exchanger 类简化数据交换Exchanger 对象的操作出奇简单:阻塞等待,直到两个独立的线程调用 exchange() 方法为止。当发生这种情况时,交换两个线程提供的数据。这种机制既优雅又简单。Exchanger 是泛型类,其声明如下:
Exchanger其中,T 指定了要进行交换的数据的类型。Exchanger 定义的唯一方法是 exchange(),该方法具有如下所示的两种形式:T exchange(T ref) throws …T exchage(T ref, long wait, TimeUnit unit) throws …其中,ref 是要进行交换的数据(对象)的引用。从另一个线程的 exchage() 方法返回。exchange() 方法的关键在于,直到同一个 Exchanger 对象被两个独立的线程分别调用后,该方法才会成功返回。使用 Phaser 类Phaser 类相当于是 CyclicBarrier 的高级版,它使用起来和 CyclicBarrier 类似,另外,还支持多阶段。也就是说,Phaser 的主要用途是同步多阶段。Phaser 定义了4个构造函数,其中常用的有两个:Phaser ( )Phaser (int numParties)第一个构造函数创建 Phaser 对象,注册 party 的数量为 0 。第二个构造函数将注册 party 的数量设置为 numparties 。术语 “party” 经常被用应用于使用 Phaser 注册的对象,相当于线程的意思。通常注册 party 的数量和将被同步的线程的数量是一致的(但非必须)。构造好 Phaser 对象后,为了注册 party,可以调用 register() 方法:int register()为了通知 party 已经完成某个阶段,必须调用 arrive() 或 其相关方法。当到达的任务数量等于注册的 party 数量时,该阶段就完成了,并且 Phaser 推进到下一个阶段(如果有的话)。int arrive()这个方法用于通知 party(通常是执行线程)已经完成了某些任务。该方法不会阻塞执行线程,如果想要阻塞等待需要调用 arriveAndAwaitAdvance() 方法。int arriveAndAwaitAdvance()通过调用 arriveAndDeregister() 方法,可以在线程到达时销毁自身。int arriveAndDeregister()为了获取当前阶段编号,可以调用 getPhase() 方法。阶段编号从 0 开始。如果 Phaser 对象已经终止,就返回一个负值。final int getPhase()原子操作并发 API 提供了一些以一种不可中断的操作(即原子操作),获取、设置以及比较变量的方法。以简化为此使用锁的情况。原子操作是通过一些类以及方法完成的,例如 AtomicInteger 和 AtomicLong 类,以及 get()、set()、compareAndSet()、decrementAndGet() 和 getAndSet() 方法,它们的功能显而易见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值