Exchanger详解

参考:Java并发编程的艺术

JDK版本:AdoptOpenJDK 11.0.10+9

1 概念

Exchanger(交换器),可以用于线程之间交换数据。

它提供一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange()方法交换数据, 如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点时,这两个线程就可以交换数据。

因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。

2 方法

Exchanger提供了一些方法,如下:

方法说明
V exchange(V x)交换数据。V是要交换的数据类型,一个线程执行了该方法,会等待另一个线程执行该方法后进行数据交换,否则一直等待。
V exchange(V x, long timeout, TimeUnit unit)带超时时间的方法。如果超时,将不再等待。

3 例子

public class ExchangerTest {

	public static void main(String[] args) {
		Exchanger<Object> exchanger = new Exchanger<>();
		Task thread1 = new Task(exchanger, "info from thread1");
		Task thread2 = new Task(exchanger, "info from thread2");
		// 启动线程
		(new Thread(thread1, "thread1")).start();
		(new Thread(thread2, "thread2")).start();
	}
	

	/**
	 * 任务类
	 */
	private static class Task implements Runnable {
		
		private Exchanger<Object> exchanger;
		
		private Object sendObject;
		
		public Task(Exchanger<Object> exchanger, Object sendObject) {
			this.exchanger = exchanger;
			this.sendObject = sendObject;
		}

		@Override
		public void run() {
			try {
				// 交换数据
				Object receiveObject = exchanger.exchange(sendObject);
				// 打印获得的数据
				System.out.println(Thread.currentThread().getName() 
                                   + " receive info: " + receiveObject.toString());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	} 
}

4 源码解析

4.1 构造函数

Exchanger只有一个构造函数:

    public Exchanger() {
        participant = new Participant();
    }

构造函数内部创建了一个Participant对象。

查看Participant源码:

    static final class Participant extends ThreadLocal<Node> {
        public Node initialValue() { return new Node(); }
    }

Participant继承了ThreadLocal,用来保存线程本地变量Node

查看Node源码:

    @jdk.internal.vm.annotation.Contended static final class Node {
        /** 以下字段,多槽交换时使用 */
        int index;              // Arena 多槽数组的索引
        int bound;              // 记录上次的bound
        int collides;           // 当前bound的CAS失败次数
        
        /** 以下字段,单槽交换时使用 */
        int hash;               // 线程的伪随机数,用于自选优化
        Object item;            // 当前线程携带的资源
        volatile Object match;  // 配对线程携带的数据
        volatile Thread parked; // 此节点上的阻塞线程
    }

可以将Node理解为线程自身携带的要交换的数据节点对象。

4.2 Exchanger数据交换方式

Exchanger有两种数据交换方式:

  • 单槽位交换(并发量低时选用)
  • 多槽位交换(并发量高时选用)

首先,查看exchange()函数源码:

    @SuppressWarnings("unchecked")
    public V exchange(V x) throws InterruptedException {
        Object v;
        Node[] a;
        // 转换null参数为空对象
        Object item = (x == null) ? NULL_ITEM : x;
        
        // 判断采用哪种交换方式
        if (((a = arena) != null ||
             (v = slotExchange(item, false, 0L)) == null) &&
            ((Thread.interrupted() || // disambiguates null return
              (v = arenaExchange(item, false, 0L)) == null)))
            throw new InterruptedException();
        return (v == NULL_ITEM) ? null : (V)v;
    }

exchange()函数的功能就是判断使用哪种交换方式:

  • 单槽位交换:如果多槽位数组为空(arena == null),采用单槽位交换方式(slotExchange);
  • 多槽位交换:如果多槽位数组不为空(arena != null),或者单槽位交换失败时,进行多槽位交换(arenaExchange)。

Exchanger有两个成员:

    private volatile Node[] arena;

    private volatile Node slot;

arena代表多槽数组,在多槽交换时使用;slot代表单槽交换节点,在单槽交换时使用。

单槽交换、多槽交换源码参考:Java多线程进阶(二一)—— J.U.C之synchronizer框架:Exchanger

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值