并发编程原理与实战(十三)详解数据交互利器Exchanger

上一篇《并发编程原理与实战(十二)并发协同利器Phaser之应用举例与总结》总结了CountDownLatch、CyclicBarrier、Semaphore、Phaser这四个多线程并发协同工具类的特性和使用场景,本文继续学习另一个并发协同利器Exchanger。Exchanger同样是java.util.concurrent包下的一个线程并发协同工具类,主要用于两个线程之间交换数据,本文就来详细学习下这个工具。

认识交换器Exchanger
/**
 * A synchronization point at which threads can pair and swap elements
 * within pairs.  Each thread presents some object on entry to the
 * {@link #exchange exchange} method, matches with a partner thread,
 * and receives its partner's object on return.  An Exchanger may be
 * viewed as a bidirectional form of a {@link SynchronousQueue}.
 * Exchangers may be useful in applications such as genetic algorithms
 * and pipeline designs.
 ...
 */ 

Exchanger的字面意思是“交换器”的意思,它提供了一个同步点,两个线程在此交换各自持有的数据。每个线程通过调用exchange()方法呈现某些(数据)对象,然后匹配一个伙伴线程,伙伴线程接收(数据)对象并返回。一个Exchanger可以当成一个双向的阻塞队列,Exchanger通常在管道设计类的应用程序中很有用处。

插槽
/*
 * This works great in principle. But in practice, like many
 * algorithms centered on atomic updates to a single location, it
 * scales horribly when there are more than a few participants
 * using the same Exchanger. So the implementation instead uses a
 * form of elimination arena, that spreads out this contention by
 * arranging that some threads typically use different slots,
 * while still ensuring that eventually, any two parties will be
 * able to exchange items.
  ...
 */ 

在多线程的场景下,多个线程使用同一个插槽是可怕的,所以Exchanger采用了消除争用的实现方式,通过安排不同的线程使用不同的插槽,最终保证两个线程都能够交换数据。插槽可以理解成 Exchanger 内部的一个‌数据暂存区‌(通常是一个变量或数组),用于临时存储线程待交换的数据对象。

单槽与多槽
/*
 * Implementing an effective arena requires allocating a bunch of
 * space, so we only do so upon detecting contention (except on
 * uniprocessors, where they wouldn't help, so aren't used).
 * Otherwise, exchanges use the single-slot slotExchange method.
 * On contention, not only must the slots be in different
 * locations, but the locations must not encounter memory
 * contention due to being on the same cache line (or more
 * generally, the same coherence unit).  
  ...
 */ 

实现一个有效的多插槽区域(arena)需要分配一堆内存,只有在检测到争用的情况下才会这样做;否则在低并发的情况下,就用一个单一的插槽进行数据交换。在争用(高并发)的情况下,不仅要插槽在不同的位置,而且在这些位置上不能因为连续的单元存在内存争用。在高并发的情况下,通过切换到多个插槽,减少线程之间对插槽的竞争。

碰撞跟踪

高并发情况下,多个线程竞争同一插槽会导致 ‌CAS 操作失败‌(CAS(Compare-And-Swap)‌ 是一种无锁的原子操作机制,支持实现线程安全的数据更新),即发生“碰撞”。

/* 
 * The arena starts out with only one used slot. We expand the
 * effective arena size by tracking collisions; i.e., failed CASes
 * while trying to exchange. By nature of the above algorithm, the
 * only kinds of collision that reliably indicate contention are
 * when two attempted releases collide -- one of two attempted
 * offers can legitimately fail to CAS without indicating
 * contention by more than one other thread. (Note: it is possible
 * but not worthwhile to more precisely detect contention by
 * reading slot values after CAS failures.)  When a thread has
 * collided at each slot within the current arena bound, it tries
 * to expand the arena size by one. We track collisions within
 * bounds by using a version (sequence) number on the "bound"
 * field, and conservatively reset collision counts when a
 * participant notices that bound has been updated (in either
 * direction).
 */  

多插槽区域通常从一个已经使用的插槽开始,通过跟踪碰撞来扩展有效的插槽区域的大小,例如当交互数据时失败。当一个线程在当前多插槽区域中的每个插槽中发生碰撞,碰撞次数超过阈值,就会尝试将多插槽区域扩大一个(触发多槽扩展),内部通过一个顺序字段来记录碰撞次数,以此来跟踪碰撞。

三阶段等待策略

spin(自旋)->yield(让步)->block‌(‌阻塞) 是处理多线程竞争插槽时渐进式的等待策略,旨在平衡 CPU 资源消耗与线程响应速度。

/* 
* The effective arena size is reduced (when there is more than
* one slot) by giving up on waiting after a while and trying to
* decrement the arena size on expiration. The value of "a while"
* is an empirical matter.  We implement by piggybacking on the
* use of spin->yield->block that is essential for reasonable
* waiting performance anyway -- in a busy exchanger, offers are
* usually almost immediately released, in which case context
* switching on multiprocessors is extremely slow/wasteful. 
...
*/ 

当线程等待了“一会儿”后还没有抢到插槽时,它可能会放弃等待并试图缩小多槽区域的大小,这个“一会儿”是一个经验值,设置等待多长通常根据实际的业务需求来确定。通过spin(自旋)->yield(让步)->block‌(‌阻塞) 策略来实现,这个策略对合理的等待是关键的。在一个繁忙的交换器中,交换通常是瞬间释放的,在这种场景下,在多处理器中上下文切换是极其缓慢或者浪费资源的。

‌1、自旋(Spin)阶段‌

线程通过CAS操作尝试抢占插槽失败后,先进入短时间(也就是“一会儿”)的忙等待(通常循环数十次),避免立即进入阻塞状态导致上下文切换,因为上下文切换是需要额外的开销的,这个短暂的循环等待通常称为自旋。

‌2、让步(Yield)阶段‌

若自旋后仍未能成功抢占插槽,调用主动让出 CPU ,允许其他线程运行。

3‌、阻塞(Block)阶段‌

长期竞争抢占插槽失败后,线程通过挂起进入阻塞状态并释放 CPU 资源,其他线程被唤醒后重新尝试交换操作。

讲解了交换器、插槽、单槽与多槽、碰撞跟踪、三阶段等待策略等概念后,下面我们来分析下Exchanger的api。

核心方法
1、构造函数
/**
 * Creates a new Exchanger.
 */
public Exchanger() {
   
   
    participant = new Participant();
}

该类中唯一的一个构造函数,创建一个交换器。

2、exchange()方法
/**
 * Waits for another thread to arrive at this exchange point (unless
 * the current thread is {@linkplain Thread#interrupt interrupted}),
 * and then transfers the given object to it, receiving its object
 * in return.
 *
 * <p>If another thread is already waiting at the exchange point then
 * it is resumed for thread scheduling purposes and receives the object
 * passed in by the current thread.  The current thread returns immediately,
 * receiving the object passed to the exchange by that other thread.
 *
 * <p>If no other thread is already waiting at the exchange then the
 * current thread is disabled for thread scheduling purposes and lies
 * dormant until one of two things happens:
 * <ul>
 * <li>Some other thread enters the exchange; or
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread.
 * </ul>
 * <p>If the current thread:
 * <ul>
 * <li>has its interrupted status set on entry to this method; or
 * <li>is {@linkplain Thread#interrupt interrupted} while waiting
 * for the exchange,
 * </ul>
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 *
 * @param x the object to exchange
 * @return the object provided by the other thread
 * @throws InterruptedException if the current thread was
 *         interrupted while waiting
 */
@SuppressWarnings("unchecked")
public V exchange(V x) throws InterruptedException {
   
   
    Object v;
    Node[] a;
    Object item = 
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帧栈

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值