Exchanger多线程交换数据

Exchanger用于两个线程之间(也可以多个线程)交换数据,交换器将自动匹配两个线程,将其数据互相传递.

public class Test {

    public static void main(String[] args) {
        doubleThread();
    }

    public static void doubleThread() {
        Exchanger<String> exchanger = new Exchanger<>();
        Thread t1 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "~~~~~~~~~~~~~" + exchanger.exchange("AAA"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        t1.setName("线程11111111111111111111111111");
        t1.start();

        Thread t2 = new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "~~~~~~~~~~~~~" + exchanger.exchange("BBB"));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        t2.setName("线程22222222222222222222222222");
        t2.start();
    }
}

 

 

先进入Exchanger的线程会阻塞等待,直到有新的线程进入,两个线程交换数据后返回.还可以使用 public V exchange(V x, long timeout, TimeUnit unit);来设置超时等待时间,如果在等待时间内还没有配对成功,则抛出异常.

构造函数,创建了一个Participant其实是ThreadLocal,了解ThreadLocal的同学会知道ThreadLocal中其实是不存储数据的,其数据都存放到Thread中,标识符为ThreadLocal值为传递的值,当前的值为Node. 

这块的关系看不懂的可以看我的另一篇博客  ThreadLocal源码

public Exchanger() {
        participant = new Participant();
    }
static final class Participant extends ThreadLocal<Node> {
        public Node initialValue() { return new Node(); }
    }
@sun.misc.Contended static final class Node {
       int index;              // Arena index
        int bound;              // Last recorded value of Exchanger.bound
        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
    }

下边我们从带超时的 exchange(v)方法查看源码,超时方法与非超时方法只是在线程park()时,传递了休眠时间,如果是被其他线程唤醒的则不会抛出异常,如果是park()睡眠时间到达后自然唤醒的则抛出异常.

public V exchange(V x) throws InterruptedException {
        Object v;
        Object item = (x == null) ? NULL_ITEM : x; //传入的是null则给默认值
        if ((arena != null ||(v = slotExchange(item, false, 0L)) == null) 
       && ((Thread.interrupted() || (v = arenaExchange(item, false, 0L)) == null))) throw new InterruptedException(); return (v == NULL_ITEM) ? null : (V)v;//如果交换线程给的数据是NULL_ITEM则表示对方给的null }

在这个方法中最主要的两个分支是 slotExchange 和  arenaExchange 前者表示线程并发低的情况,例如A,B两个线程,则不会触发arena机制,无论A,B哪个线程先到达,只需要等待另外一个线程到达与之交换数据即可.而后者则是Exchanger处于高并发情况,线程

数量很多需要使用arena数组存放.

 

 

先分析slotExchange

当线程1到达标记1时,先检查slot是否为null,简单说在低并发情况下slot就是表示等待线程的,如果slot为null则表示没有需要配对的线程,那当前线程就需要需要等待.随后自旋到标记2将自己设置为slot.

出了自旋后,走到标记3, match如果有值则表示有线程与之交换.因为此时就一条线程所以继续走到标记4将调用park,将自己阻塞.

此时线程2也到达,也是到达标记1,检查到slot已经有值是线程1,则先将slot设置为null然后将自己要交换的数据设置到线程1的math,在标记5处唤醒线程1,

而自己则将从线程1处获取的交换数据返回.

线程1被唤醒后重新自旋到标记3获取到交换数据,走到标记6完成交换

在整个slotExchange里因为多线程原因,某条线程获取到slot后想通过CAS的方式将其设置为null从而交换数据,但发现slot已经不是自己获取的也就是被其他变量抢先交换了.那就会触发标记7创建arena从而导致使用arena交换策略.

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) {//标记1
if (U.compareAndSwapObject(this, SLOT, q, null)) {
Object v = q.item;
q.match = item;
Thread w = q.parked;
if (w != null)
U.unpark(w);//标记5
return v;
}
// create arena on contention, but continue until slot null
if (NCPU > 1 && bound == 0 &&
U.compareAndSwapInt(this, BOUND, 0, SEQ))
arena = new Node[(FULL + 2) << ASHIFT];//标记7
}
else if (arena != null)
return null; // caller must reroute to arenaExchange
else {//标记2
p.item = item;
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) {//标记3
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)
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);//标记4
p.parked = null;
U.putObject(t, BLOCKER, null);
}
else if (U.compareAndSwapObject(this, SLOT, p, null)) {
v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
break;
}
}
U.putOrderedObject(p, MATCH, null);//标记6
p.item = null;
p.hash = h;
return v;
}

arenaExchange策略一旦触发则需要配对的线程就不在设置到slot属性上了,而是存档到arena数组中.arenaExchange策略也就是从数组中寻找能与之交换的线程.

线程1走到标记1拿到自己的index它默认为0也就是从arena的0号下标开始,到标记2处寻找数组中是否有其他等待的线程,当前肯定是没有则到达标记3将自身存档到数组中,然后到达标记4阻塞线程

当线程2走到标记1拿到自己的index后到标记2寻找数组中是否有等待的线程,此时发现线程1处于等待.此处的交换数据与slotExchange相同,最终唤醒线程1.

线程1唤醒后自旋到标记5判断已经有线程与之交换则拿到数据返回.

 

    private final Object arenaExchange(Object item, boolean timed, long ns) {
        Node[] a = arena;
        Node p = participant.get();
        for (int i = p.index;;) { //标记1                     // access slot at i
            int b, m, c; long j;                       // j is raw array offset
            Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);//标记2
            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)) {//标记3
                    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) {//标记5
                            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
                        }
                        else if (U.getObjectVolatile(a, j) != p)
                            spins = SPINS;       // releaser hasn't set match yet
                        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);//标记4
                            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
                    p.item = null;                     // clear offer
            }
            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多线程交换的两种策略,本文中主要标记了两种策略的核心流程,源码中对于两种策略中可能发生的情况处理的更细腻,特别是arena策略实际情况如果触发了arena策略觉不是本文所描述的两个线程这么简单.

有想要详细了解的同学可以自行查看源码,也可以和我互相交流.

这篇博客送给孙航,感谢我的朋友.

posted @ 2023-02-19 18:33  顶风少年  阅读(63)  评论(0编辑  收藏  举报
返回顶部