Exchanger 交换器

128 阅读3分钟
Exchanger交换器用于线程安全的交换两个线程之间的数据。

使用示例

/*
	示例仅表示两个线程单次交换,还会有多个线程之间交换的情况。
*/
public static void main(String[] args) throws InterruptedException {
        Exchanger<String> exChanger = new Exchanger<>();

        Runnable runnable1 = () -> {
            String runner1Msg = "runner1Msg abcd";
            try {
                String exchange = exChanger.exchange(runner1Msg);
                System.out.println(Thread.currentThread().getName() + " 交换到的数据 -> " + exchange);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };

        Runnable runnable2 = () -> {
            String runner2Msg = "runner2Msg xyz";
            try {
                TimeUnit.SECONDS.sleep(10); // 加工数据的时间
                String exchange = exChanger.exchange(runner2Msg);
                System.out.println(Thread.currentThread().getName() + " 交换到的数据 -> " + exchange);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        };

        Thread t1 = new Thread(runnable1, "runnable1");
        Thread t2 = new Thread(runnable2, "runnable2");

        t1.start();
        t2.start();

        t1.join();
        t2.join();
    }

简略源码备注解析

/*
        这个方法主要处理的是没有线程争用,也就是只有两个教程交换数据的场景
     */
    private final Object slotExchange(Object item, boolean timed, long ns) {
        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) { // slot 不为空则说明已经有线程在等待了【因为是两个线程之间交换,所以需要两个线程】
                if (SLOT.compareAndSet(this, q, null)) { // 置空,在这里尝试与等待交换的线程交换数据
                    Object v = q.item;
                    q.match = item;  // 将当前slot的 realse值设置为item
                    Thread w = q.parked;
                    if (w != null) // Node 已经有线程在等待则唤醒
                        LockSupport.unpark(w);
                    return v;
                }
                // create arena on contention, but continue until slot null
                // 发生线程争用则开启 arena
                if (NCPU > 1 && bound == 0 &&
                    BOUND.compareAndSet(this, 0, SEQ))
                    arena = new Node[(FULL + 2) << ASHIFT]; // 开辟一个足够大的槽
            }
            else if (arena != null) // 直接取 arenaExchange 处理多线程争用场景
                return null; // caller must reroute to arenaExchange
            else { // 这里是发现slot为空,则自己去尝试占有这个槽等待其他线程
                p.item = item;
                if (SLOT.compareAndSet(this, null, p))
                    break;
                p.item = null;
            }
        }

        // await release  执行到这里说明当前线程已经获取到了 slot 并且没发生争用,在此等待交换操作
        int h = p.hash;
        long end = timed ? System.nanoTime() + ns : 0L;
        int spins = (NCPU > 1) ? SPINS : 1;
        Object v;
        while ((v = p.match) == null) { // 如果设置后则返回match值
            /*
                自旋自旋再自旋,这里有一个优化点就是会根据CPU核数来控制自旋次数,如果是单核直接就尝试一次避免自旋过多占用资源影响其他执行线程
             */
            if (spins > 0) {
                h ^= h << 1; h ^= h >>> 3; h ^= h << 10;  // 计算 hash
                if (h == 0)
                    h = SPINS | (int)t.getId();
                else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0) // 让出线程执行时间片
                    Thread.yield();
            }
            /*
                如果等待过程中发现了当前槽不是自己了,则增加自旋次数继续自旋等待
             */
            else if (slot != p)
                spins = SPINS;
            /*
                自旋一段之后如果线程没有被中断并且arena没有 并且超时时间未过的
                则进入查看阻塞避免持续的自旋开销
             */
            else if (!t.isInterrupted() && arena == null &&
                     (!timed || (ns = end - System.nanoTime()) > 0L)) {
                p.parked = t;
                if (slot == p) { // 如果当前槽为当前线程Node则阻塞等待被唤醒
                    if (ns == 0L)
                        LockSupport.park(this);
                    else
                        LockSupport.parkNanos(this, ns);
                }
                p.parked = null;
            }
            /*
                尝试将slot槽换成空
             */
            else if (SLOT.compareAndSet(this, p, null)) {
                v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
                break;
            }
        }
        MATCH.setRelease(p, null); // 设置释放的匹配数据
        p.item = null;
        p.hash = h;
        return v;
    }
/*
        多线程争用的场景
     */
    private final Object arenaExchange(Object item, boolean timed, long ns) {
        Node[] a = arena;
        int alen = a.length;
        Node p = participant.get();
        for (int i = p.index;;) {                      // access slot at i
            int b, m, c;
            int j = (i << ASHIFT) + ((1 << ASHIFT) - 1); // 插入槽位置 j
            if (j < 0 || j >= alen)
                j = alen - 1;
            Node q = (Node)AA.getAcquire(a, j); // 获取槽位置的Node,
            /*
                尝试与当前槽内的Node交换数据
             */
            if (q != null && AA.compareAndSet(a, j, q, null)) {
                Object v = q.item;                     // release
                q.match = item;
                Thread w = q.parked;
                if (w != null)
                    LockSupport.unpark(w);
                return v;
            }
            /*
                这里判断选择的槽位置是否为空与下标是否在范围内,如果为空则自己去占住
             */
            else if (i <= (m = (b = bound) & MMASK) && q == null) {
                p.item = item;                         // offer
                /*
                    尝试将自己加入这个等待的槽位,可以占住槽为之后使用slotExchanger相同的规则
                 */
                if (AA.compareAndSet(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) { // 已经设置了match 则执行交换返回
                            MATCH.setRelease(p, 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
                        }
                        else if (AA.getAcquire(a, j) != p)
                            spins = SPINS;       // releaser hasn't set match yet
                        else if (!t.isInterrupted() && m == 0 &&
                                 (!timed ||
                                  (ns = end - System.nanoTime()) > 0L)) {
                            p.parked = t;              // minimize window
                            if (AA.getAcquire(a, j) == p) {
                                if (ns == 0L)
                                    LockSupport.park(this);
                                else
                                    LockSupport.parkNanos(this, ns);
                            }
                            p.parked = null;
                        }
                        else if (AA.getAcquire(a, j) == p &&
                                 AA.compareAndSet(a, j, p, null)) {
                            if (m != 0)                // try to shrink
                                BOUND.compareAndSet(this, 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
                    p.item = null;                     // clear offer
            }
            /*
                交换数据失败情况 or 申请下标不在范围内 的情况下进入更新信息,
                e.g. 这里是争用失败的情况下查找新的槽位
             */
            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 ||
                         !BOUND.compareAndSet(this, b, b + SEQ + 1)) {
                    p.collides = c + 1;
                    i = (i == 0) ? m : i - 1;          // cyclically traverse
                }
                else
                    i = m + 1;                         // grow
                p.index = i;
            }
        }
    }