【Java并发编程实战】——Exchanger

Exchanger 提供一个同步点给其他线程来配对交换数据。每一个线程都会调用exchange 方法并交出自己的元素,等待其他的线程到达交换点来配对,配对后获取和自己配对的线程的元素并返回。它可以看做是 SynchronousQueue 的双向形式。

看个简单的例子:启动两个线程,交换各自的元素列表

/**
 * Created by Tangwz on 2018/12/4.
 */
public class TestExchange {
    public static void main(String[] args) {
        Exchanger<List<Integer>> exchanger = new Exchanger<>();
        MyThread myThread1 = new MyThread(exchanger, Arrays.asList(1, 2), "myThread1");
        MyThread myThread2 = new MyThread(exchanger, Arrays.asList(2, 3), "myThread2");
        myThread1.start();
        myThread2.start();
    }

    private static class MyThread extends Thread {
        private Exchanger<List<Integer>> exchanger;
        private List<Integer> elements;

        private MyThread(Exchanger<List<Integer>> exchanger, List<Integer> elements, String name) {
            super(name);
            this.exchanger = exchanger;
            this.elements = elements;
        }

        @Override
        public void run() {
            List<Integer> exchange = new ArrayList<>();
            try {
                exchange = exchanger.exchange(elements);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + Arrays.deepToString(exchange.toArray()));
        }
    }
}

输出结果:

myThread1:[2, 3]
myThread2:[1, 2]

exchange 方法,等待其他线程到达交换点(直到被中断),然后把元素x传递给此线程,并接受这个线程的元素返回。
如果其他线程T1早已等待,那么T1恢复执行并接受当前线程传递过来的值,当前线程获取到T1传递的之后立即返回;
如果没有其他线程等待,则阻塞当前线程,等待其他线程到达或者被中断。

public V exchange(V x) throws InterruptedException {
    Object v;
    Object item = (x == null) ? NULL_ITEM : x; // translate null args
    if ((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;
}

Exchanger 主要属性

//arena 竞争平台中每个槽位之间的字节移位距离
private static final int ASHIFT = 7;

//arena 竞争平台最大可支持的索引下标
private static final int MMASK = 0xff;

//边界字段的序列
private static final int SEQ = MMASK + 1;

private static final int NCPU = Runtime.getRuntime().availableProcessors();

//arena 竞争平台最大可支持的槽位数量,虚拟机可用处理器数量NCPU为4,则FULL为2
static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1;

//等待被匹配的自旋边界值
private static final int SPINS = 1 << 10;

//exchange API 不允许交换的元素为空
private static final Object NULL_ITEM = new Object();

//超时设置的哨兵
private static final Object TIMED_OUT = new Object();

/**
 * Nodes hold partially exchanged data, plus other per-thread
 * bookkeeping. Padded via @sun.misc.Contended to reduce memory
 * contention.
 */
@sun.misc.Contended static final class Node {
	//arena的下标
    int index;              // Arena index
    //上一次Exchanger.bound的值
    int bound;              // Last recorded value of Exchanger.bound
    //当前bound下CAS失败的次数
    int collides;           // Number of CAS failures at current bound
    //自旋用的伪随机数
    int hash;               // Pseudo-random for spins
    Object item;            // This thread's current item
    //其他和此节点交换的线程会把自己的元素放入到这里
    volatile Object match;  // Item provided by releasing thread
    //当前线程如果没等到其他交换线程来交换,设置当前线程到这里
    volatile Thread parked; // Set to this thread when parked, else null
}

/** The corresponding thread local class */
static final class Participant extends ThreadLocal<Node> {
    public Node initialValue() { return new Node(); }
}

//每个线程的状态
private final Participant participant;
//竞争平台
private volatile Node[] arena;
//槽 用于竞争交换元素
private volatile Node slot;
//竞争平台最大有效索引
private volatile int bound;

//获取数组第一个元素的偏移地址
private static final int ABASE;
private static final sun.misc.Unsafe U;
static{
	U = sun.misc.Unsafe.getUnsafe();
	Class<?> ak = Node[].class;
	// ABASE absorbs padding in front of element 0
	ABASE= U.arrayBaseOffset(ak) + (1 << ASHIFT);
}

单节点交换

    /**
     * Exchange function used until arenas enabled. See above for explanation.
     *
     * @param item the item to exchange
     * @param timed true if the wait is timed
     * @param ns if timed, the maximum wait time, else 0L
     * @return the other thread's item; or null if either the arena
     * was enabled or the thread was interrupted before completion; or
     * TIMED_OUT if timed and timed out
     */
    private final Object slotExchange(Object item, boolean timed, long ns) {
    	//participant  ThreadLocal<Node> 
        Node p = participant.get();
        Thread t = Thread.currentThread();
        //如果交换前被中断,则退出
        if (t.isInterrupted()) // preserve interrupt status so caller can recheck
            return null;

        for (Node q;;) {
            if ((q = slot) != null) {
        		//已经有线程到达交换点,尝试交换
                if (U.compareAndSwapObject(this, SLOT, q, null)) {
                	//获取交换点线程传递的元素
                    Object v = q.item;
                   	//设置自己的元素给交换点
                    q.match = item;
                    Thread w = q.parked;
                    //若交换点的线程被阻塞,唤醒它
                    if (w != null)
                        U.unpark(w);
                    return v;
                }
                //若失败,说明有其他线程竞争匹配到了处于交换点的线程
                // create arena on contention, but continue until slot null
                if (NCPU > 1 && bound == 0 &&
                    U.compareAndSwapInt(this, BOUND, 0, SEQ))
                    //多处理器,创建一个竞争平台,给当前线程再新增一个槽位
                    //初始化的实际可用槽个数为(FULL+2)
                    arena = new Node[(FULL + 2) << ASHIFT];
            }
        	//没有线程到达交换点执行到这里
            else if (arena != null)
            	//平台不为空,切换到平台上去竞争
                return null; // caller must reroute to arenaExchange
            else {
                p.item = item;
            	//尝试将自己设置成 SLOT
                if (U.compareAndSwapObject(this, SLOT, null, p))
                	//退出循环,等待被匹配
                    break;
                //若竞争失败,重新循环
                p.item = null;
            }
        }

        // await release
        //等待被匹配,先自旋一下,不阻塞
        int h = p.hash;
        long end = timed ? System.nanoTime() + ns : 0L;
        //按照实际的机器配置设置循环次数
        int spins = (NCPU > 1) ? SPINS : 1;
        Object v;
        //检查有没有被匹配
        while ((v = p.match) == null) {
        	//没有其他线程进来匹配,自旋
            if (spins > 0) {
            	//生成伪随机数
                h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
                if (h == 0)
                    h = SPINS | (int)t.getId();
                else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
                    Thread.yield();
            }
            else if (slot != p)
            	//其他线程匹配了本线程,但是还没有修改 match 字段,再自旋等待一下
                spins = SPINS;
            else if (!t.isInterrupted() && arena == null &&
                     (!timed || (ns = end - System.nanoTime()) > 0L)) {
                //没有被中断且竞争平台为空且没有超时
                U.putObject(t, BLOCKER, this);
                p.parked = t;
                //判断有没有被匹配
                if (slot == p)
                	//还没有被配置,阻塞本线程
                    U.park(false, ns);
                //恢复执行后,清空 parked 和 BLOCKER 字段
                p.parked = null;
                U.putObject(t, BLOCKER, null);
            }
            //上面判断不通过,先清空 SLOT
            else if (U.compareAndSwapObject(this, SLOT, p, null)) {
            	//判断是超时还是中断,返回不同的信息
                v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
                break;
            }
        }
        //被匹配,清空 MATCH 字段
        U.putOrderedObject(p, MATCH, null);
        p.item = null;
        p.hash = h;
        //返回交换过来的元素
        return v;
    }

上面如果说竞争激烈,槽位不足以两两交换,那么转移到竞争平台上取交换数据。
看上面初始化的代码

arena = new Node[(FULL + 2) << ASHIFT];

偏移 ASHIFT 是为了保证两个槽不会在同一个缓存行,避免伪共享。

什么是伪共享?
CPU核心间交换数据以缓存行为最小单位,多个变量会被放入一个缓存行,这个缓存行可以被许多线程访问,任何一个线程修改缓存行内的某个数据,根据缓存一致性协议,其他线程需要重新加载整个缓存行。

如何避免伪共享?
第一种方式,创建一个变量的时候使用填充字段填充该变量所在的缓存行,这样就避免了多个变量存在同一个缓存行;
第二种,java8提供了 @Contended 注解,这个注解暗示JVM应当将字段放入不同的缓存行。

在竞争平台上进行数据交换,这部分代码较为复杂,这里只看个大概。

/**
 * Exchange function when arenas enabled. See above for explanation.
 *
 * @param item the (non-null) item to exchange
 * @param timed true if the wait is timed
 * @param ns if timed, the maximum wait time, else 0L
 * @return the other thread's item; or null if interrupted; or
 * TIMED_OUT if timed and timed out
 */
private final Object arenaExchange(Object item, boolean timed, long ns) {
    Node[] a = arena;
    Node p = participant.get();
    for (int i = p.index;;) {                      // access slot at i
        int b, m, c; long j;                       // j is raw array offset
        //根据自己目前的index到平台中去找相应位置的槽做交换
        Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
        //判断槽点有无其它线程进来,并尝试交换
        if (q != null && U.compareAndSwapObject(a, j, q, null)) {
        	//交换,并唤醒槽点线程,返回
            Object v = q.item;                     // release
            q.match = item;
            Thread w = q.parked;
            if (w != null)
                U.unpark(w);
            return v;
        }
        //无线程等待交换
        else if (i <= (m = (b = bound) & MMASK) && q == null) {
            p.item = item;                         // offer
            //尝试将自己设置成槽点等待其他线程进来
            if (U.compareAndSwapObject(a, j, null, p)) {
                long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;
                Thread t = Thread.currentThread(); // wait
                //设置成功之后自旋
                for (int h = p.hash, spins = SPINS;;) {
                    Object v = p.match;
                    //若自旋期间有线程进来匹配,获取后退出
                    if (v != null) {
                        U.putOrderedObject(p, MATCH, null);
                        p.item = null;             // clear for next use
                        p.hash = h;
                        return v;
                    }
                    else if (spins > 0) {
                        h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
                        if (h == 0)                // initialize hash
                            h = SPINS | (int)t.getId();
                        else if (h < 0 &&          // approx 50% true
                                 (--spins & ((SPINS >>> 1) - 1)) == 0)
                            Thread.yield();        // two yields per wait
                    }
                    //匹配了但是还没有设置 match 继续自旋
                    else if (U.getObjectVolatile(a, j) != p)
                        spins = SPINS;       // releaser hasn't set match yet
                    //m减小到0说明平台上的槽位都不能和自己进行匹配
                    else if (!t.isInterrupted() && m == 0 &&
                             (!timed ||
                              (ns = end - System.nanoTime()) > 0L)) {
                        //自旋一段时间后,判断是否阻塞自己
                        U.putObject(t, BLOCKER, this); // emulate LockSupport
                        p.parked = t;              // minimize window
                        if (U.getObjectVolatile(a, j) == p)
                            U.park(false, ns);
                        p.parked = null;
                        U.putObject(t, BLOCKER, null);
                    }
                    //准备调到下个槽位试试
                    else if (U.getObjectVolatile(a, j) == p &&
                             U.compareAndSwapObject(a, j, p, null)) {
                        if (m != 0)                // try to shrink
                            U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);
                        p.item = null;
                        p.hash = h;
                        //修改自己在平台上的索引
                        i = p.index >>>= 1;        // descend
                        if (Thread.interrupted())
                            return null;
                        if (timed && m == 0 && ns <= 0L)
                            return TIMED_OUT;
                        break;                     // expired; restart
                    }
                }
            }
            else
            	//设置成槽位交换点失败,清空 item
                p.item = null;                     // clear offer
        }
        //i不在有效值范围,重新更新i值
        else {
            if (p.bound != b) {                    // stale; reset
                p.bound = b;
                p.collides = 0;
                i = (i != m || m == 0) ? m : m - 1;
            }
            else if ((c = p.collides) < m || m == FULL ||
                     !U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {
                p.collides = c + 1;
                i = (i == 0) ? m : i - 1;          // cyclically traverse
            }
            else
                i = m + 1;                         // grow
            p.index = i;
        }
    }
}

Exchanger 和 SynchronousQueue 的区别
SynchronousQueue 匹配发生在队列头节点,操作类型不匹配才会进行配对,某一类操作较多的话会进入到阻塞队列;
Exchanger 不区分生产者和消费者,只要是节点都可以交换,所有线程先在单个槽点上交换,若并发量大则另外在平台上开辟槽点给线程进行交换,匹配的操作在所有槽点上。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值